+```
+
+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
diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
new file mode 100644
index 0000000..53d7f4b
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
@@ -0,0 +1,28 @@
+
+ *
+ * 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;
diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-emit-once.php b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-once.php
new file mode 100644
index 0000000..74f4d17
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-once.php
@@ -0,0 +1,30 @@
+
+ *
+ * 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;
diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
new file mode 100644
index 0000000..39fc4ba
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
@@ -0,0 +1,28 @@
+
+ *
+ * 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;
diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-emit.php b/assets/php/vendor/evenement/evenement/examples/benchmark-emit.php
new file mode 100644
index 0000000..3ab639e
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/examples/benchmark-emit.php
@@ -0,0 +1,28 @@
+
+ *
+ * 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;
diff --git a/assets/php/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php b/assets/php/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php
new file mode 100644
index 0000000..414be3b
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php
@@ -0,0 +1,39 @@
+
+ *
+ * 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();
+
+$listeners = [];
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $listeners[] = function ($a, $b, $c) {};
+}
+
+$start = microtime(true);
+foreach ($listeners as $listener) {
+ $emitter->once('event', $listener);
+}
+$time = microtime(true) - $start;
+echo 'Adding ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
+
+$start = microtime(true);
+foreach ($listeners as $listener) {
+ $emitter->removeListener('event', $listener);
+}
+$time = microtime(true) - $start;
+echo 'Removing ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/assets/php/vendor/evenement/evenement/phpunit.xml.dist b/assets/php/vendor/evenement/evenement/phpunit.xml.dist
new file mode 100644
index 0000000..70bc693
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/phpunit.xml.dist
@@ -0,0 +1,24 @@
+
+
+
+
+
+ ./tests/Evenement/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitter.php b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitter.php
new file mode 100644
index 0000000..db189b9
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitter.php
@@ -0,0 +1,17 @@
+
+ *
+ * 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;
+}
diff --git a/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
new file mode 100644
index 0000000..310631a
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * 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 = null);
+ public function emit($event, array $arguments = []);
+}
diff --git a/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
new file mode 100644
index 0000000..a78e65c
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
@@ -0,0 +1,135 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+use InvalidArgumentException;
+
+trait EventEmitterTrait
+{
+ protected $listeners = [];
+ protected $onceListeners = [];
+
+ public function on($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->listeners[$event])) {
+ $this->listeners[$event] = [];
+ }
+
+ $this->listeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function once($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->onceListeners[$event])) {
+ $this->onceListeners[$event] = [];
+ }
+
+ $this->onceListeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function removeListener($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ 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]);
+ }
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $index = \array_search($listener, $this->onceListeners[$event], true);
+ if (false !== $index) {
+ unset($this->onceListeners[$event][$index]);
+ if (\count($this->onceListeners[$event]) === 0) {
+ unset($this->onceListeners[$event]);
+ }
+ }
+ }
+ }
+
+ public function removeAllListeners($event = null)
+ {
+ if ($event !== null) {
+ unset($this->listeners[$event]);
+ } else {
+ $this->listeners = [];
+ }
+
+ if ($event !== null) {
+ unset($this->onceListeners[$event]);
+ } else {
+ $this->onceListeners = [];
+ }
+ }
+
+ public function listeners($event = null): array
+ {
+ if ($event === null) {
+ $events = [];
+ $eventNames = \array_unique(
+ \array_merge(\array_keys($this->listeners), \array_keys($this->onceListeners))
+ );
+ foreach ($eventNames as $eventName) {
+ $events[$eventName] = \array_merge(
+ isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [],
+ isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : []
+ );
+ }
+ return $events;
+ }
+
+ return \array_merge(
+ isset($this->listeners[$event]) ? $this->listeners[$event] : [],
+ isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : []
+ );
+ }
+
+ public function emit($event, array $arguments = [])
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (isset($this->listeners[$event])) {
+ foreach ($this->listeners[$event] as $listener) {
+ $listener(...$arguments);
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $listeners = $this->onceListeners[$event];
+ unset($this->onceListeners[$event]);
+ foreach ($listeners as $listener) {
+ $listener(...$arguments);
+ }
+ }
+ }
+}
diff --git a/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
new file mode 100644
index 0000000..28f3011
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
@@ -0,0 +1,438 @@
+
+ *
+ * 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 InvalidArgumentException;
+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']);
+ }
+
+ public function testListeners()
+ {
+ $onA = function () {};
+ $onB = function () {};
+ $onC = function () {};
+ $onceA = function () {};
+ $onceB = function () {};
+ $onceC = function () {};
+
+ self::assertCount(0, $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onA);
+ self::assertCount(1, $this->emitter->listeners('event'));
+ self::assertSame([$onA], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceA);
+ self::assertCount(2, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceB);
+ self::assertCount(3, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onceA, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onB);
+ self::assertCount(4, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceA, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->removeListener('event', $onceA);
+ self::assertCount(3, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceC);
+ self::assertCount(4, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceB, $onceC], $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onC);
+ self::assertCount(5, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onC, $onceB, $onceC], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceA);
+ self::assertCount(6, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->removeListener('event', $onB);
+ self::assertCount(5, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->emit('event');
+ self::assertCount(2, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onC], $this->emitter->listeners('event'));
+ }
+
+ public function testOnceCallIsNotRemovedWhenWorkingOverOnceListeners()
+ {
+ $aCalled = false;
+ $aCallable = function () use (&$aCalled) {
+ $aCalled = true;
+ };
+ $bCalled = false;
+ $bCallable = function () use (&$bCalled, $aCallable) {
+ $bCalled = true;
+ $this->emitter->once('event', $aCallable);
+ };
+ $this->emitter->once('event', $bCallable);
+
+ self::assertFalse($aCalled);
+ self::assertFalse($bCalled);
+ $this->emitter->emit('event');
+
+ self::assertFalse($aCalled);
+ self::assertTrue($bCalled);
+ $this->emitter->emit('event');
+
+ self::assertTrue($aCalled);
+ self::assertTrue($bCalled);
+ }
+
+ public function testEventNameMustBeStringOn()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->on(null, function () {});
+ }
+
+ public function testEventNameMustBeStringOnce()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->once(null, function () {});
+ }
+
+ public function testEventNameMustBeStringRemoveListener()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->removeListener(null, function () {});
+ }
+
+ public function testEventNameMustBeStringEmit()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->emit(null);
+ }
+
+ public function testListenersGetAll()
+ {
+ $a = function () {};
+ $b = function () {};
+ $c = function () {};
+ $d = function () {};
+
+ $this->emitter->once('event2', $c);
+ $this->emitter->on('event', $a);
+ $this->emitter->once('event', $b);
+ $this->emitter->on('event', $c);
+ $this->emitter->once('event', $d);
+
+ self::assertSame(
+ [
+ 'event' => [
+ $a,
+ $c,
+ $b,
+ $d,
+ ],
+ 'event2' => [
+ $c,
+ ],
+ ],
+ $this->emitter->listeners()
+ );
+ }
+
+ public function testOnceNestedCallRegression()
+ {
+ $first = 0;
+ $second = 0;
+
+ $this->emitter->once('event', function () use (&$first, &$second) {
+ $first++;
+ $this->emitter->once('event', function () use (&$second) {
+ $second++;
+ });
+ $this->emitter->emit('event');
+ });
+ $this->emitter->emit('event');
+
+ self::assertSame(1, $first);
+ self::assertSame(1, $second);
+ }
+}
diff --git a/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
new file mode 100644
index 0000000..df17424
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
@@ -0,0 +1,51 @@
+
+ *
+ * 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;
+ }
+}
diff --git a/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/functions.php b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/functions.php
new file mode 100644
index 0000000..7f11f5b
--- /dev/null
+++ b/assets/php/vendor/evenement/evenement/tests/Evenement/Tests/functions.php
@@ -0,0 +1,17 @@
+
+ *
+ * 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;
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/CHANGELOG.md b/assets/php/vendor/guzzlehttp/psr7/CHANGELOG.md
new file mode 100644
index 0000000..5c252b3
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/CHANGELOG.md
@@ -0,0 +1,110 @@
+# CHANGELOG
+
+## 1.4.2 - 2017-03-20
+
+* Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing
+ calls to `trigger_error` when deprecated methods are invoked.
+
+## 1.4.1 - 2017-02-27
+
+* Reverted BC break by reintroducing behavior to automagically fix a URI with a
+ relative path and an authority by adding a leading slash to the path. It's only
+ deprecated now.
+* Added triggering of silenced deprecation warnings.
+
+## 1.4.0 - 2017-02-21
+
+* Fix `Stream::read` when length parameter <= 0.
+* `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
+* Fix `ServerRequest::getUriFromGlobals` when `Host` header contains port.
+* Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form.
+* Allow `parse_response` to parse a response without delimiting space and reason.
+* Ensure each URI modification results in a valid URI according to PSR-7 discussions.
+ Invalid modifications will throw an exception instead of returning a wrong URI or
+ doing some magic.
+ - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception
+ because the path of a URI with an authority must start with a slash "/" or be empty
+ - `(new Uri())->withScheme('http')` will return `'http://localhost'`
+* Fix compatibility of URIs with `file` scheme and empty host.
+* Added common URI utility methods based on RFC 3986 (see documentation in the readme):
+ - `Uri::isDefaultPort`
+ - `Uri::isAbsolute`
+ - `Uri::isNetworkPathReference`
+ - `Uri::isAbsolutePathReference`
+ - `Uri::isRelativePathReference`
+ - `Uri::isSameDocumentReference`
+ - `Uri::composeComponents`
+ - `UriNormalizer::normalize`
+ - `UriNormalizer::isEquivalent`
+ - `UriResolver::relativize`
+* Deprecated `Uri::resolve` in favor of `UriResolver::resolve`
+* Deprecated `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
+
+## 1.3.1 - 2016-06-25
+
+* Fix `Uri::__toString` for network path references, e.g. `//example.org`.
+* Fix missing lowercase normalization for host.
+* Fix handling of URI components in case they are `'0'` in a lot of places,
+ e.g. as a user info password.
+* Fix `Uri::withAddedHeader` to correctly merge headers with different case.
+* Fix trimming of header values in `Uri::withAddedHeader`. Header values may
+ be surrounded by whitespace which should be ignored according to RFC 7230
+ Section 3.2.4. This does not apply to header names.
+* Fix `Uri::withAddedHeader` with an array of header values.
+* Fix `Uri::resolve` when base path has no slash and handling of fragment.
+* Fix handling of encoding in `Uri::with(out)QueryValue` so one can pass the
+ key/value both in encoded as well as decoded form to those methods. This is
+ consistent with withPath, withQuery etc.
+* Fix `ServerRequest::withoutAttribute` when attribute value is null.
+
+## 1.3.0 - 2016-04-13
+
+* Added remaining interfaces needed for full PSR7 compatibility
+ (ServerRequestInterface, UploadedFileInterface, etc.).
+* Added support for stream_for from scalars.
+* Can now extend Uri.
+* Fixed a bug in validating request methods by making it more permissive.
+
+## 1.2.3 - 2016-02-18
+
+* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
+ streams, which can sometimes return fewer bytes than requested with `fread`.
+* Fixed handling of gzipped responses with FNAME headers.
+
+## 1.2.2 - 2016-01-22
+
+* Added support for URIs without any authority.
+* Added support for HTTP 451 'Unavailable For Legal Reasons.'
+* Added support for using '0' as a filename.
+* Added support for including non-standard ports in Host headers.
+
+## 1.2.1 - 2015-11-02
+
+* Now supporting negative offsets when seeking to SEEK_END.
+
+## 1.2.0 - 2015-08-15
+
+* Body as `"0"` is now properly added to a response.
+* Now allowing forward seeking in CachingStream.
+* Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+* functions.php is now conditionally required.
+* user-info is no longer dropped when resolving URIs.
+
+## 1.1.0 - 2015-06-24
+
+* URIs can now be relative.
+* `multipart/form-data` headers are now overridden case-insensitively.
+* URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+* A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
diff --git a/assets/php/vendor/guzzlehttp/psr7/LICENSE b/assets/php/vendor/guzzlehttp/psr7/LICENSE
new file mode 100644
index 0000000..581d95f
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling
+
+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.
diff --git a/assets/php/vendor/guzzlehttp/psr7/README.md b/assets/php/vendor/guzzlehttp/psr7/README.md
new file mode 100644
index 0000000..1649935
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/README.md
@@ -0,0 +1,739 @@
+# PSR-7 Message Implementation
+
+This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing.
+
+
+[](https://travis-ci.org/guzzle/psr7)
+
+
+# Stream implementation
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## AppendStream
+
+`GuzzleHttp\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use GuzzleHttp\Psr7;
+
+$a = Psr7\stream_for('abc, ');
+$b = Psr7\stream_for('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\stream_for(' Above all listen to me'));
+
+echo $composed; // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`GuzzleHttp\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use GuzzleHttp\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`GuzzleHttp\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use GuzzleHttp\Psr7;
+
+// Create an empty stream
+$stream = Psr7\stream_for();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$dropping->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`GuzzleHttp\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing
+to create a concrete class for a simple extension point.
+
+```php
+
+use GuzzleHttp\Psr7;
+
+$stream = Psr7\stream_for('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`GuzzleHttp\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
+
+This stream decorator skips the first 10 bytes of the given stream to remove
+the gzip header, converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`GuzzleHttp\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use GuzzleHttp\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`GuzzleHttp\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`GuzzleHttp\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`GuzzleHttp\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`GuzzleHttp\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use GuzzleHttp\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ call_user_func($this->callback);
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use GuzzleHttp\Psr7\StreamWrapper;
+
+$stream = GuzzleHttp\Psr7\stream_for('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Function API
+
+There are various functions available under the `GuzzleHttp\Psr7` namespace.
+
+
+## `function str`
+
+`function str(MessageInterface $message)`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
+echo GuzzleHttp\Psr7\str($request);
+```
+
+
+## `function uri_for`
+
+`function uri_for($uri)`
+
+This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
+UriInterface for the given value. If the value is already a `UriInterface`, it
+is returned as-is.
+
+```php
+$uri = GuzzleHttp\Psr7\uri_for('http://example.com');
+assert($uri === GuzzleHttp\Psr7\uri_for($uri));
+```
+
+
+## `function stream_for`
+
+`function stream_for($resource = '', array $options = [])`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+* - metadata: Array of custom metadata.
+* - size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = GuzzleHttp\Psr7\stream_for('foo');
+$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r'));
+
+$generator function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = GuzzleHttp\Psr7\stream_for($generator(100));
+```
+
+
+## `function parse_header`
+
+`function parse_header($header)`
+
+Parse an array of header values containing ";" separated data into an array of
+associative arrays representing the header key value pair data of the header.
+When a parameter does not contain a value, but just contains a key, this
+function will inject a key with a '' string value.
+
+
+## `function normalize_header`
+
+`function normalize_header($header)`
+
+Converts an array of header values that may contain comma separated headers
+into an array of headers with no comma separated values.
+
+
+## `function modify_request`
+
+`function modify_request(RequestInterface $request, array $changes)`
+
+Clone and modify a request with the given changes. This method is useful for
+reducing the number of clones needed to mutate a message.
+
+The changes can be one of:
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `function rewind_body`
+
+`function rewind_body(MessageInterface $message)`
+
+Attempts to rewind a message body and throws an exception on failure. The body
+of the message will only be rewound if a call to `tell()` returns a value other
+than `0`.
+
+
+## `function try_fopen`
+
+`function try_fopen($filename, $mode)`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an error
+handler that checks for errors and throws an exception instead.
+
+
+## `function copy_to_string`
+
+`function copy_to_string(StreamInterface $stream, $maxLen = -1)`
+
+Copy the contents of a stream into a string until the given number of bytes
+have been read.
+
+
+## `function copy_to_stream`
+
+`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`
+
+Copy the contents of a stream into another stream until the given number of
+bytes have been read.
+
+
+## `function hash`
+
+`function hash(StreamInterface $stream, $algo, $rawOutput = false)`
+
+Calculate a hash of a Stream. This method reads the entire stream to calculate
+a rolling hash (based on PHP's hash_init functions).
+
+
+## `function readline`
+
+`function readline(StreamInterface $stream, $maxLength = null)`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `function parse_request`
+
+`function parse_request($message)`
+
+Parses a request message string into a request object.
+
+
+## `function parse_response`
+
+`function parse_response($message)`
+
+Parses a response message string into a response object.
+
+
+## `function parse_query`
+
+`function parse_query($str, $urlEncoding = true)`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key value pair
+will become an array. This function does not parse nested PHP style arrays into
+an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
+`['foo[a]' => '1', 'foo[b]' => '2']`).
+
+
+## `function build_query`
+
+`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of parse_query() to build a query string.
+This function does not modify the provided keys when an array is encountered
+(like http_build_query would).
+
+
+## `function mimetype_from_filename`
+
+`function mimetype_from_filename($filename)`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `function mimetype_from_extension`
+
+`function mimetype_from_extension($extension)`
+
+Maps a file extensions to a mimetype.
+
+
+# Additional URI Methods
+
+Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
+this library also provides additional functionality when working with URIs as static methods.
+
+## URI Types
+
+An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
+An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
+the base URI. Relative references can be divided into several forms according to
+[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2):
+
+- network-path references, e.g. `//example.com/path`
+- absolute-path references, e.g. `/path`
+- relative-path references, e.g. `subpath`
+
+The following methods can be used to identify the type of the URI.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolute`
+
+`public static function isAbsolute(UriInterface $uri): bool`
+
+Whether the URI is absolute, i.e. it has a scheme.
+
+### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
+
+`public static function isNetworkPathReference(UriInterface $uri): bool`
+
+Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
+termed an network-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
+
+`public static function isAbsolutePathReference(UriInterface $uri): bool`
+
+Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
+termed an absolute-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
+
+`public static function isRelativePathReference(UriInterface $uri): bool`
+
+Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
+termed a relative-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
+
+`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
+
+Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
+fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
+(apart from its fragment) is considered a same-document reference.
+
+## URI Components
+
+Additional methods to work with URI components.
+
+### `GuzzleHttp\Psr7\Uri::isDefaultPort`
+
+`public static function isDefaultPort(UriInterface $uri): bool`
+
+Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
+or the standard port. This method can be used independently of the implementation.
+
+### `GuzzleHttp\Psr7\Uri::composeComponents`
+
+`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
+
+Composes a URI reference string from its various components according to
+[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
+manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
+
+### `GuzzleHttp\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts): UriInterface`
+
+Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components.
+
+
+### `GuzzleHttp\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
+
+Creates a new URI with a specific query string value. Any existing query string values that exactly match the
+provided key are removed and replaced with the given key value pair. A value of null will set the query string
+key without a value, e.g. "key" instead of "key=value".
+
+
+### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
+
+Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
+provided key are removed.
+
+## Reference Resolution
+
+`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
+to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
+do when resolving a link in a website based on the current request URI.
+
+### `GuzzleHttp\Psr7\UriResolver::resolve`
+
+`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
+
+Converts the relative URI into a new URI that is resolved against the base URI.
+
+### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
+
+`public static function removeDotSegments(string $path): string`
+
+Removes dot segments from a path and returns the new path according to
+[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
+
+### `GuzzleHttp\Psr7\UriResolver::relativize`
+
+`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
+
+Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
+
+```php
+(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+```
+
+One use-case is to use the current request URI as base URI and then generate relative links in your documents
+to reduce the document size or offer self-contained downloadable document archives.
+
+```php
+$base = new Uri('http://example.com/a/b/');
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+```
+
+## Normalization and Comparison
+
+`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
+[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
+
+### `GuzzleHttp\Psr7\UriNormalizer::normalize`
+
+`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
+
+Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
+This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
+of normalizations to apply. The following normalizations are available:
+
+- `UriNormalizer::PRESERVING_NORMALIZATIONS`
+
+ Default normalizations which only include the ones that preserve semantics.
+
+- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
+
+ All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
+
+ Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b`
+
+- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
+
+ Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
+ ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
+ not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
+ characters by URI normalizers.
+
+ Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/`
+
+- `UriNormalizer::CONVERT_EMPTY_PATH`
+
+ Converts the empty path to "/" for http and https URIs.
+
+ Example: `http://example.org` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DEFAULT_HOST`
+
+ Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
+ "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
+ RFC 3986.
+
+ Example: `file://localhost/myfile` → `file:///myfile`
+
+- `UriNormalizer::REMOVE_DEFAULT_PORT`
+
+ Removes the default port of the given URI scheme from the URI.
+
+ Example: `http://example.org:80/` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DOT_SEGMENTS`
+
+ Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
+ change the semantics of the URI reference.
+
+ Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html`
+
+- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
+
+ Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
+ and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
+ may change the semantics. Encoded slashes (%2F) are not removed.
+
+ Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html`
+
+- `UriNormalizer::SORT_QUERY_PARAMETERS`
+
+ Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
+ significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
+ of the URI.
+
+ Example: `?lang=en&article=fred` → `?article=fred&lang=en`
+
+### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
+
+`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
+
+Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
+`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
+This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
+equivalence or difference of relative references does not mean anything.
diff --git a/assets/php/vendor/guzzlehttp/psr7/composer.json b/assets/php/vendor/guzzlehttp/psr7/composer.json
new file mode 100644
index 0000000..b1c5a90
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/composer.json
@@ -0,0 +1,39 @@
+{
+ "name": "guzzlehttp/psr7",
+ "type": "library",
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": ["request", "response", "message", "stream", "http", "uri", "url"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/AppendStream.php b/assets/php/vendor/guzzlehttp/psr7/src/AppendStream.php
new file mode 100644
index 0000000..23039fd
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/AppendStream.php
@@ -0,0 +1,233 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = [];
+ }
+
+ /**
+ * Detaches each attached stream
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->close();
+ $this->detached = true;
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ . $i . ' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ $this->current++;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ // Using a loose comparison here to match on '', false, and null
+ if ($result == null) {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : [];
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/BufferStream.php b/assets/php/vendor/guzzlehttp/psr7/src/BufferStream.php
new file mode 100644
index 0000000..af4d4c2
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/BufferStream.php
@@ -0,0 +1,137 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ // TODO: What should happen here?
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : [];
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/CachingStream.php b/assets/php/vendor/guzzlehttp/psr7/src/CachingStream.php
new file mode 100644
index 0000000..ed68f08
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/CachingStream.php
@@ -0,0 +1,138 @@
+remoteStream = $stream;
+ $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence == SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ $byte = $size + $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // Read the remoteStream until we have read in at least the amount
+ // of bytes requested, or we reach the end of the file.
+ while ($diff > 0 && !$this->remoteStream->eof()) {
+ $this->read($diff);
+ $diff = $byte - $this->stream->getSize();
+ }
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+
+ private function cacheEntireStream()
+ {
+ $target = new FnStream(['write' => 'strlen']);
+ copy_to_stream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/DroppingStream.php b/assets/php/vendor/guzzlehttp/psr7/src/DroppingStream.php
new file mode 100644
index 0000000..8935c80
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/DroppingStream.php
@@ -0,0 +1,42 @@
+stream = $stream;
+ $this->maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/FnStream.php b/assets/php/vendor/guzzlehttp/psr7/src/FnStream.php
new file mode 100644
index 0000000..cc9b445
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/FnStream.php
@@ -0,0 +1,149 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = [$stream, $diff];
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function rewind()
+ {
+ call_user_func($this->_fn_rewind);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/InflateStream.php b/assets/php/vendor/guzzlehttp/psr7/src/InflateStream.php
new file mode 100644
index 0000000..0051d3f
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/InflateStream.php
@@ -0,0 +1,52 @@
+read(10);
+ $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
+ // Skip the header, that is 10 + length of filename + 1 (nil) bytes
+ $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
+ $resource = StreamWrapper::getResource($stream);
+ stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
+ $this->stream = new Stream($resource);
+ }
+
+ /**
+ * @param StreamInterface $stream
+ * @param $header
+ * @return int
+ */
+ private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
+ {
+ $filename_header_length = 0;
+
+ if (substr(bin2hex($header), 6, 2) === '08') {
+ // we have a filename, read until nil
+ $filename_header_length = 1;
+ while ($stream->read(1) !== chr(0)) {
+ $filename_header_length++;
+ }
+ }
+
+ return $filename_header_length;
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/assets/php/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
new file mode 100644
index 0000000..02cec3a
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
@@ -0,0 +1,39 @@
+filename = $filename;
+ $this->mode = $mode;
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return stream_for(try_fopen($this->filename, $this->mode));
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/LimitStream.php b/assets/php/vendor/guzzlehttp/psr7/src/LimitStream.php
new file mode 100644
index 0000000..3c13d4f
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -0,0 +1,155 @@
+stream = $stream;
+ $this->setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset % with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/MessageTrait.php b/assets/php/vendor/guzzlehttp/psr7/src/MessageTrait.php
new file mode 100644
index 0000000..1e4da64
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -0,0 +1,183 @@
+ array of values */
+ private $headers = [];
+
+ /** @var array Map of lowercase header name => original name at registration */
+ private $headerNames = [];
+
+ /** @var string */
+ private $protocol = '1.1';
+
+ /** @var StreamInterface */
+ private $stream;
+
+ public function getProtocolVersion()
+ {
+ return $this->protocol;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+ return $new;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headerNames[strtolower($header)]);
+ }
+
+ public function getHeader($header)
+ {
+ $header = strtolower($header);
+
+ if (!isset($this->headerNames[$header])) {
+ return [];
+ }
+
+ $header = $this->headerNames[$header];
+
+ return $this->headers[$header];
+ }
+
+ public function getHeaderLine($header)
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value)
+ {
+ if (!is_array($value)) {
+ $value = [$value];
+ }
+
+ $value = $this->trimHeaderValues($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ unset($new->headers[$new->headerNames[$normalized]]);
+ }
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value)
+ {
+ if (!is_array($value)) {
+ $value = [$value];
+ }
+
+ $value = $this->trimHeaderValues($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $new->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+ }
+
+ return $new;
+ }
+
+ public function withoutHeader($header)
+ {
+ $normalized = strtolower($header);
+
+ if (!isset($this->headerNames[$normalized])) {
+ return $this;
+ }
+
+ $header = $this->headerNames[$normalized];
+
+ $new = clone $this;
+ unset($new->headers[$header], $new->headerNames[$normalized]);
+
+ return $new;
+ }
+
+ public function getBody()
+ {
+ if (!$this->stream) {
+ $this->stream = stream_for('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+ return $new;
+ }
+
+ private function setHeaders(array $headers)
+ {
+ $this->headerNames = $this->headers = [];
+ foreach ($headers as $header => $value) {
+ if (!is_array($value)) {
+ $value = [$value];
+ }
+
+ $value = $this->trimHeaderValues($value);
+ $normalized = strtolower($header);
+ if (isset($this->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $this->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $this->headerNames[$normalized] = $header;
+ $this->headers[$header] = $value;
+ }
+ }
+ }
+
+ /**
+ * Trims whitespace from the header values.
+ *
+ * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
+ *
+ * header-field = field-name ":" OWS field-value OWS
+ * OWS = *( SP / HTAB )
+ *
+ * @param string[] $values Header values
+ *
+ * @return string[] Trimmed header values
+ *
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ */
+ private function trimHeaderValues(array $values)
+ {
+ return array_map(function ($value) {
+ return trim($value, " \t");
+ }, $values);
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/MultipartStream.php b/assets/php/vendor/guzzlehttp/psr7/src/MultipartStream.php
new file mode 100644
index 0000000..c0fd584
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/MultipartStream.php
@@ -0,0 +1,153 @@
+boundary = $boundary ?: sha1(uniqid('', true));
+ $this->stream = $this->createStream($elements);
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements)
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element)
+ {
+ foreach (['contents', 'name'] as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = stream_for($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if (substr($uri, 0, 6) !== 'php://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ list($body, $headers) = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ isset($element['filename']) ? $element['filename'] : null,
+ isset($element['headers']) ? $element['headers'] : []
+ );
+
+ $stream->addStream(stream_for($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(stream_for("\r\n"));
+ }
+
+ /**
+ * @return array
+ */
+ private function createElement($name, StreamInterface $stream, $filename, array $headers)
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = ($filename === '0' || $filename)
+ ? sprintf('form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename))
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type && ($filename === '0' || $filename)) {
+ if ($type = mimetype_from_filename($filename)) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower($k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/assets/php/vendor/guzzlehttp/psr7/src/NoSeekStream.php
new file mode 100644
index 0000000..2332218
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/NoSeekStream.php
@@ -0,0 +1,22 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ try {
+ return copy_to_string($this);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/Request.php b/assets/php/vendor/guzzlehttp/psr7/src/Request.php
new file mode 100644
index 0000000..0828548
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/Request.php
@@ -0,0 +1,142 @@
+method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $version;
+
+ if (!$this->hasHeader('Host')) {
+ $this->updateHostFromUri();
+ }
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+ }
+
+ public function getRequestTarget()
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target == '') {
+ $target = '/';
+ }
+ if ($this->uri->getQuery() != '') {
+ $target .= '?' . $this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+ return $new;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ $new = clone $this;
+ $new->method = strtoupper($method);
+ return $new;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost) {
+ $new->updateHostFromUri();
+ }
+
+ return $new;
+ }
+
+ private function updateHostFromUri()
+ {
+ $host = $this->uri->getHost();
+
+ if ($host == '') {
+ return;
+ }
+
+ if (($port = $this->uri->getPort()) !== null) {
+ $host .= ':' . $port;
+ }
+
+ if (isset($this->headerNames['host'])) {
+ $header = $this->headerNames['host'];
+ } else {
+ $header = 'Host';
+ $this->headerNames['host'] = 'Host';
+ }
+ // Ensure Host is the first header.
+ // See: http://tools.ietf.org/html/rfc7230#section-5.4
+ $this->headers = [$header => [$host]] + $this->headers;
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/Response.php b/assets/php/vendor/guzzlehttp/psr7/src/Response.php
new file mode 100644
index 0000000..2830c6c
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/Response.php
@@ -0,0 +1,132 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /** @var string */
+ private $reasonPhrase = '';
+
+ /** @var int */
+ private $statusCode = 200;
+
+ /**
+ * @param int $status Status code
+ * @param array $headers Response headers
+ * @param string|null|resource|StreamInterface $body Response body
+ * @param string $version Protocol version
+ * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
+ */
+ public function __construct(
+ $status = 200,
+ array $headers = [],
+ $body = null,
+ $version = '1.1',
+ $reason = null
+ ) {
+ $this->statusCode = (int) $status;
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+
+ $this->setHeaders($headers);
+ if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
+ $this->reasonPhrase = self::$phrases[$this->statusCode];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $new = clone $this;
+ $new->statusCode = (int) $code;
+ if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
+ $reasonPhrase = self::$phrases[$new->statusCode];
+ }
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/ServerRequest.php b/assets/php/vendor/guzzlehttp/psr7/src/ServerRequest.php
new file mode 100644
index 0000000..575aab8
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/ServerRequest.php
@@ -0,0 +1,358 @@
+serverParams = $serverParams;
+
+ parent::__construct($method, $uri, $headers, $body, $version);
+ }
+
+ /**
+ * Return an UploadedFile instance array.
+ *
+ * @param array $files A array which respect $_FILES structure
+ * @throws InvalidArgumentException for unrecognized values
+ * @return array
+ */
+ public static function normalizeFiles(array $files)
+ {
+ $normalized = [];
+
+ foreach ($files as $key => $value) {
+ if ($value instanceof UploadedFileInterface) {
+ $normalized[$key] = $value;
+ } elseif (is_array($value) && isset($value['tmp_name'])) {
+ $normalized[$key] = self::createUploadedFileFromSpec($value);
+ } elseif (is_array($value)) {
+ $normalized[$key] = self::normalizeFiles($value);
+ continue;
+ } else {
+ throw new InvalidArgumentException('Invalid value in files specification');
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Create and return an UploadedFile instance from a $_FILES specification.
+ *
+ * If the specification represents an array of values, this method will
+ * delegate to normalizeNestedFileSpec() and return that return value.
+ *
+ * @param array $value $_FILES struct
+ * @return array|UploadedFileInterface
+ */
+ private static function createUploadedFileFromSpec(array $value)
+ {
+ if (is_array($value['tmp_name'])) {
+ return self::normalizeNestedFileSpec($value);
+ }
+
+ return new UploadedFile(
+ $value['tmp_name'],
+ (int) $value['size'],
+ (int) $value['error'],
+ $value['name'],
+ $value['type']
+ );
+ }
+
+ /**
+ * Normalize an array of file specifications.
+ *
+ * Loops through all nested files and returns a normalized array of
+ * UploadedFileInterface instances.
+ *
+ * @param array $files
+ * @return UploadedFileInterface[]
+ */
+ private static function normalizeNestedFileSpec(array $files = [])
+ {
+ $normalizedFiles = [];
+
+ foreach (array_keys($files['tmp_name']) as $key) {
+ $spec = [
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key],
+ 'error' => $files['error'][$key],
+ 'name' => $files['name'][$key],
+ 'type' => $files['type'][$key],
+ ];
+ $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+ }
+
+ return $normalizedFiles;
+ }
+
+ /**
+ * Return a ServerRequest populated with superglobals:
+ * $_GET
+ * $_POST
+ * $_COOKIE
+ * $_FILES
+ * $_SERVER
+ *
+ * @return ServerRequestInterface
+ */
+ public static function fromGlobals()
+ {
+ $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
+ $headers = function_exists('getallheaders') ? getallheaders() : [];
+ $uri = self::getUriFromGlobals();
+ $body = new LazyOpenStream('php://input', 'r+');
+ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+
+ $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+ return $serverRequest
+ ->withCookieParams($_COOKIE)
+ ->withQueryParams($_GET)
+ ->withParsedBody($_POST)
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
+ /**
+ * Get a Uri populated with values from $_SERVER.
+ *
+ * @return UriInterface
+ */
+ public static function getUriFromGlobals() {
+ $uri = new Uri('');
+
+ $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+
+ $hasPort = false;
+ if (isset($_SERVER['HTTP_HOST'])) {
+ $hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']);
+ $uri = $uri->withHost($hostHeaderParts[0]);
+ if (isset($hostHeaderParts[1])) {
+ $hasPort = true;
+ $uri = $uri->withPort($hostHeaderParts[1]);
+ }
+ } elseif (isset($_SERVER['SERVER_NAME'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_NAME']);
+ } elseif (isset($_SERVER['SERVER_ADDR'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+ }
+
+ if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+ $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+ }
+
+ $hasQuery = false;
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $requestUriParts = explode('?', $_SERVER['REQUEST_URI']);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+ $uri = $uri->withQuery($requestUriParts[1]);
+ }
+ }
+
+ if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+ $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+ }
+
+ return $uri;
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUploadedFiles()
+ {
+ return $this->uploadedFiles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->uploadedFiles = $uploadedFiles;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCookieParams()
+ {
+ return $this->cookieParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookieParams = $cookies;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttribute($attribute, $default = null)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $default;
+ }
+
+ return $this->attributes[$attribute];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withAttribute($attribute, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$attribute] = $value;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withoutAttribute($attribute)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ unset($new->attributes[$attribute]);
+
+ return $new;
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/Stream.php b/assets/php/vendor/guzzlehttp/psr7/src/Stream.php
new file mode 100644
index 0000000..e336628
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/Stream.php
@@ -0,0 +1,257 @@
+ [
+ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
+ 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
+ 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a+' => true
+ ],
+ 'write' => [
+ 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
+ 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
+ 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
+ ]
+ ];
+
+ /**
+ * This constructor accepts an associative array of options.
+ *
+ * - size: (int) If a read stream would otherwise have an indeterminate
+ * size, but the size is known due to foreknowledge, then you can
+ * provide that size, in bytes.
+ * - metadata: (array) Any additional metadata to return when the metadata
+ * of the stream is accessed.
+ *
+ * @param resource $stream Stream resource to wrap.
+ * @param array $options Associative array of options.
+ *
+ * @throws \InvalidArgumentException if the stream is not a stream resource
+ */
+ public function __construct($stream, $options = [])
+ {
+ if (!is_resource($stream)) {
+ throw new \InvalidArgumentException('Stream must be a resource');
+ }
+
+ if (isset($options['size'])) {
+ $this->size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : [];
+
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
+ $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ throw new \RuntimeException('The stream is detached');
+ }
+
+ throw new \BadMethodCallException('No value for ' . $name);
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return (string) stream_get_contents($this->stream);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ $contents = stream_get_contents($this->stream);
+
+ if ($contents === false) {
+ throw new \RuntimeException('Unable to read stream contents');
+ }
+
+ return $contents;
+ }
+
+ public function close()
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ return !$this->stream || feof($this->stream);
+ }
+
+ public function tell()
+ {
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ } elseif (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ . $offset . ' with whence ' . var_export($whence, true));
+ }
+ }
+
+ public function read($length)
+ {
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+ if ($length < 0) {
+ throw new \RuntimeException('Length parameter cannot be negative');
+ }
+
+ if (0 === $length) {
+ return '';
+ }
+
+ $string = fread($this->stream, $length);
+ if (false === $string) {
+ throw new \RuntimeException('Unable to read from stream');
+ }
+
+ return $string;
+ }
+
+ public function write($string)
+ {
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : [];
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/assets/php/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
new file mode 100644
index 0000000..daec6f5
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,149 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @param string $name Name of the property (allows "stream" only).
+ *
+ * @return StreamInterface
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array([$this->stream, $method], $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * Implement in subclasses to dynamically create streams when requested.
+ *
+ * @return StreamInterface
+ * @throws \BadMethodCallException
+ */
+ protected function createStream()
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/assets/php/vendor/guzzlehttp/psr7/src/StreamWrapper.php
new file mode 100644
index 0000000..cf7b223
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/StreamWrapper.php
@@ -0,0 +1,121 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, stream_context_create([
+ 'guzzle' => ['stream' => $stream]
+ ]));
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = [
+ 'r' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/UploadedFile.php b/assets/php/vendor/guzzlehttp/psr7/src/UploadedFile.php
new file mode 100644
index 0000000..e62bd5c
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/UploadedFile.php
@@ -0,0 +1,316 @@
+setError($errorStatus);
+ $this->setSize($size);
+ $this->setClientFilename($clientFilename);
+ $this->setClientMediaType($clientMediaType);
+
+ if ($this->isOk()) {
+ $this->setStreamOrFile($streamOrFile);
+ }
+ }
+
+ /**
+ * Depending on the value set file or stream variable
+ *
+ * @param mixed $streamOrFile
+ * @throws InvalidArgumentException
+ */
+ private function setStreamOrFile($streamOrFile)
+ {
+ if (is_string($streamOrFile)) {
+ $this->file = $streamOrFile;
+ } elseif (is_resource($streamOrFile)) {
+ $this->stream = new Stream($streamOrFile);
+ } elseif ($streamOrFile instanceof StreamInterface) {
+ $this->stream = $streamOrFile;
+ } else {
+ throw new InvalidArgumentException(
+ 'Invalid stream or file provided for UploadedFile'
+ );
+ }
+ }
+
+ /**
+ * @param int $error
+ * @throws InvalidArgumentException
+ */
+ private function setError($error)
+ {
+ if (false === is_int($error)) {
+ throw new InvalidArgumentException(
+ 'Upload file error status must be an integer'
+ );
+ }
+
+ if (false === in_array($error, UploadedFile::$errors)) {
+ throw new InvalidArgumentException(
+ 'Invalid error status for UploadedFile'
+ );
+ }
+
+ $this->error = $error;
+ }
+
+ /**
+ * @param int $size
+ * @throws InvalidArgumentException
+ */
+ private function setSize($size)
+ {
+ if (false === is_int($size)) {
+ throw new InvalidArgumentException(
+ 'Upload file size must be an integer'
+ );
+ }
+
+ $this->size = $size;
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringOrNull($param)
+ {
+ return in_array(gettype($param), ['string', 'NULL']);
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringNotEmpty($param)
+ {
+ return is_string($param) && false === empty($param);
+ }
+
+ /**
+ * @param string|null $clientFilename
+ * @throws InvalidArgumentException
+ */
+ private function setClientFilename($clientFilename)
+ {
+ if (false === $this->isStringOrNull($clientFilename)) {
+ throw new InvalidArgumentException(
+ 'Upload file client filename must be a string or null'
+ );
+ }
+
+ $this->clientFilename = $clientFilename;
+ }
+
+ /**
+ * @param string|null $clientMediaType
+ * @throws InvalidArgumentException
+ */
+ private function setClientMediaType($clientMediaType)
+ {
+ if (false === $this->isStringOrNull($clientMediaType)) {
+ throw new InvalidArgumentException(
+ 'Upload file client media type must be a string or null'
+ );
+ }
+
+ $this->clientMediaType = $clientMediaType;
+ }
+
+ /**
+ * Return true if there is no upload error
+ *
+ * @return boolean
+ */
+ private function isOk()
+ {
+ return $this->error === UPLOAD_ERR_OK;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isMoved()
+ {
+ return $this->moved;
+ }
+
+ /**
+ * @throws RuntimeException if is moved or not ok
+ */
+ private function validateActive()
+ {
+ if (false === $this->isOk()) {
+ throw new RuntimeException('Cannot retrieve stream due to upload error');
+ }
+
+ if ($this->isMoved()) {
+ throw new RuntimeException('Cannot retrieve stream after it has already been moved');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws RuntimeException if the upload was not successful.
+ */
+ public function getStream()
+ {
+ $this->validateActive();
+
+ if ($this->stream instanceof StreamInterface) {
+ return $this->stream;
+ }
+
+ return new LazyOpenStream($this->file, 'r+');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/is_uploaded_file
+ * @see http://php.net/move_uploaded_file
+ * @param string $targetPath Path to which to move the uploaded file.
+ * @throws RuntimeException if the upload was not successful.
+ * @throws InvalidArgumentException if the $path specified is invalid.
+ * @throws RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
+ */
+ public function moveTo($targetPath)
+ {
+ $this->validateActive();
+
+ if (false === $this->isStringNotEmpty($targetPath)) {
+ throw new InvalidArgumentException(
+ 'Invalid path provided for move operation; must be a non-empty string'
+ );
+ }
+
+ if ($this->file) {
+ $this->moved = php_sapi_name() == 'cli'
+ ? rename($this->file, $targetPath)
+ : move_uploaded_file($this->file, $targetPath);
+ } else {
+ copy_to_stream(
+ $this->getStream(),
+ new LazyOpenStream($targetPath, 'w')
+ );
+
+ $this->moved = true;
+ }
+
+ if (false === $this->moved) {
+ throw new RuntimeException(
+ sprintf('Uploaded file could not be moved to %s', $targetPath)
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return int|null The file size in bytes or null if unknown.
+ */
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/manual/en/features.file-upload.errors.php
+ * @return int One of PHP's UPLOAD_ERR_XXX constants.
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return string|null The filename sent by the client or null if none
+ * was provided.
+ */
+ public function getClientFilename()
+ {
+ return $this->clientFilename;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClientMediaType()
+ {
+ return $this->clientMediaType;
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/Uri.php b/assets/php/vendor/guzzlehttp/psr7/src/Uri.php
new file mode 100644
index 0000000..f46c1db
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/Uri.php
@@ -0,0 +1,702 @@
+ 80,
+ 'https' => 443,
+ 'ftp' => 21,
+ 'gopher' => 70,
+ 'nntp' => 119,
+ 'news' => 119,
+ 'telnet' => 23,
+ 'tn3270' => 23,
+ 'imap' => 143,
+ 'pop' => 110,
+ 'ldap' => 389,
+ ];
+
+ private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ private static $replaceQuery = ['=' => '%3D', '&' => '%26'];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /**
+ * @param string $uri URI to parse
+ */
+ public function __construct($uri = '')
+ {
+ // weak type check to also accept null until we can add scalar type hints
+ if ($uri != '') {
+ $parts = parse_url($uri);
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ public function __toString()
+ {
+ return self::composeComponents(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * Composes a URI reference string from its various components.
+ *
+ * Usually this method does not need to be called manually but instead is used indirectly via
+ * `Psr\Http\Message\UriInterface::__toString`.
+ *
+ * PSR-7 UriInterface treats an empty component the same as a missing component as
+ * getQuery(), getFragment() etc. always return a string. This explains the slight
+ * difference to RFC 3986 Section 5.3.
+ *
+ * Another adjustment is that the authority separator is added even when the authority is missing/empty
+ * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
+ * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
+ * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
+ * that format).
+ *
+ * @param string $scheme
+ * @param string $authority
+ * @param string $path
+ * @param string $query
+ * @param string $fragment
+ *
+ * @return string
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-5.3
+ */
+ public static function composeComponents($scheme, $authority, $path, $query, $fragment)
+ {
+ $uri = '';
+
+ // weak type checks to also accept null until we can add scalar type hints
+ if ($scheme != '') {
+ $uri .= $scheme . ':';
+ }
+
+ if ($authority != ''|| $scheme === 'file') {
+ $uri .= '//' . $authority;
+ }
+
+ $uri .= $path;
+
+ if ($query != '') {
+ $uri .= '?' . $query;
+ }
+
+ if ($fragment != '') {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether the URI has the default port of the current scheme.
+ *
+ * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
+ * independently of the implementation.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ */
+ public static function isDefaultPort(UriInterface $uri)
+ {
+ return $uri->getPort() === null
+ || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
+ }
+
+ /**
+ * Whether the URI is absolute, i.e. it has a scheme.
+ *
+ * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+ * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+ * to another URI, the base URI. Relative references can be divided into several forms:
+ * - network-path references, e.g. '//example.com/path'
+ * - absolute-path references, e.g. '/path'
+ * - relative-path references, e.g. 'subpath'
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @see Uri::isNetworkPathReference
+ * @see Uri::isAbsolutePathReference
+ * @see Uri::isRelativePathReference
+ * @link https://tools.ietf.org/html/rfc3986#section-4
+ */
+ public static function isAbsolute(UriInterface $uri)
+ {
+ return $uri->getScheme() !== '';
+ }
+
+ /**
+ * Whether the URI is a network-path reference.
+ *
+ * A relative reference that begins with two slash characters is termed an network-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isNetworkPathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === '' && $uri->getAuthority() !== '';
+ }
+
+ /**
+ * Whether the URI is a absolute-path reference.
+ *
+ * A relative reference that begins with a single slash character is termed an absolute-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isAbsolutePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && isset($uri->getPath()[0])
+ && $uri->getPath()[0] === '/';
+ }
+
+ /**
+ * Whether the URI is a relative-path reference.
+ *
+ * A relative reference that does not begin with a slash character is termed a relative-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isRelativePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
+ }
+
+ /**
+ * Whether the URI is a same-document reference.
+ *
+ * A same-document reference refers to a URI that is, aside from its fragment
+ * component, identical to the base URI. When no base URI is given, only an empty
+ * URI reference (apart from its fragment) is considered a same-document reference.
+ *
+ * @param UriInterface $uri The URI to check
+ * @param UriInterface|null $base An optional base URI to compare against
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.4
+ */
+ public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
+ {
+ if ($base !== null) {
+ $uri = UriResolver::resolve($base, $uri);
+
+ return ($uri->getScheme() === $base->getScheme())
+ && ($uri->getAuthority() === $base->getAuthority())
+ && ($uri->getPath() === $base->getPath())
+ && ($uri->getQuery() === $base->getQuery());
+ }
+
+ return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
+ }
+
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
+ * @see UriResolver::removeDotSegments
+ */
+ public static function removeDotSegments($path)
+ {
+ return UriResolver::removeDotSegments($path);
+ }
+
+ /**
+ * Converts the relative URI into a new URI that is resolved against the base URI.
+ *
+ * @param UriInterface $base Base URI
+ * @param string|UriInterface $rel Relative URI
+ *
+ * @return UriInterface
+ *
+ * @deprecated since version 1.4. Use UriResolver::resolve instead.
+ * @see UriResolver::resolve
+ */
+ public static function resolve(UriInterface $base, $rel)
+ {
+ if (!($rel instanceof UriInterface)) {
+ $rel = new self($rel);
+ }
+
+ return UriResolver::resolve($base, $rel);
+ }
+
+ /**
+ * Creates a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key to remove.
+ *
+ * @return UriInterface
+ */
+ public static function withoutQueryValue(UriInterface $uri, $key)
+ {
+ $current = $uri->getQuery();
+ if ($current === '') {
+ return $uri;
+ }
+
+ $decodedKey = rawurldecode($key);
+ $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
+ return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
+ });
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * A value of null will set the query string key without a value, e.g. "key"
+ * instead of "key=value".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string|null $value Value to set
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValue(UriInterface $uri, $key, $value)
+ {
+ $current = $uri->getQuery();
+
+ if ($current === '') {
+ $result = [];
+ } else {
+ $decodedKey = rawurldecode($key);
+ $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
+ return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
+ });
+ }
+
+ // Query string separators ("=", "&") within the key or value need to be encoded
+ // (while preventing double-encoding) before setting the query string. All other
+ // chars that need percent-encoding will be encoded by withQuery().
+ $key = strtr($key, self::$replaceQuery);
+
+ if ($value !== null) {
+ $result[] = $key . '=' . strtr($value, self::$replaceQuery);
+ } else {
+ $result[] = $key;
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a URI from a hash of `parse_url` components.
+ *
+ * @param array $parts
+ *
+ * @return UriInterface
+ * @link http://php.net/manual/en/function.parse-url.php
+ *
+ * @throws \InvalidArgumentException If the components do not form a valid URI.
+ */
+ public static function fromParts(array $parts)
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ $uri->validateState();
+
+ return $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ $authority = $this->host;
+ if ($this->userInfo !== '') {
+ $authority = $this->userInfo . '@' . $authority;
+ }
+
+ if ($this->port !== null) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ $info = $user;
+ if ($password != '') {
+ $info .= ':' . $password;
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withHost($host)
+ {
+ $host = $this->filterHost($host);
+
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPort($port)
+ {
+ $port = $this->filterPort($port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPath($path)
+ {
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withQuery($query)
+ {
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+
+ return $new;
+ }
+
+ public function withFragment($fragment)
+ {
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+
+ return $new;
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param array $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
+ $this->host = isset($parts['host'])
+ ? $this->filterHost($parts['host'])
+ : '';
+ $this->port = isset($parts['port'])
+ ? $this->filterPort($parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':' . $parts['pass'];
+ }
+
+ $this->removeDefaultPort();
+ }
+
+ /**
+ * @param string $scheme
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the scheme is invalid.
+ */
+ private function filterScheme($scheme)
+ {
+ if (!is_string($scheme)) {
+ throw new \InvalidArgumentException('Scheme must be a string');
+ }
+
+ return strtolower($scheme);
+ }
+
+ /**
+ * @param string $host
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the host is invalid.
+ */
+ private function filterHost($host)
+ {
+ if (!is_string($host)) {
+ throw new \InvalidArgumentException('Host must be a string');
+ }
+
+ return strtolower($host);
+ }
+
+ /**
+ * @param int|null $port
+ *
+ * @return int|null
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($port)
+ {
+ if ($port === null) {
+ return null;
+ }
+
+ $port = (int) $port;
+ if (1 > $port || 0xffff < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
+ );
+ }
+
+ return $port;
+ }
+
+ private function removeDefaultPort()
+ {
+ if ($this->port !== null && self::isDefaultPort($this)) {
+ $this->port = null;
+ }
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the path is invalid.
+ */
+ private function filterPath($path)
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException('Path must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param string $str
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the query or fragment is invalid.
+ */
+ private function filterQueryAndFragment($str)
+ {
+ if (!is_string($str)) {
+ throw new \InvalidArgumentException('Query and fragment must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match)
+ {
+ return rawurlencode($match[0]);
+ }
+
+ private function validateState()
+ {
+ if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+ $this->host = self::HTTP_DEFAULT_HOST;
+ }
+
+ if ($this->getAuthority() === '') {
+ if (0 === strpos($this->path, '//')) {
+ throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
+ }
+ if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+ throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
+ }
+ } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
+ @trigger_error(
+ 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
+ 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
+ E_USER_DEPRECATED
+ );
+ $this->path = '/'. $this->path;
+ //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
+ }
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/assets/php/vendor/guzzlehttp/psr7/src/UriNormalizer.php
new file mode 100644
index 0000000..384c29e
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/UriNormalizer.php
@@ -0,0 +1,216 @@
+getPath() === '' &&
+ ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
+ ) {
+ $uri = $uri->withPath('/');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
+ $uri = $uri->withHost('');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
+ $uri = $uri->withPort(null);
+ }
+
+ if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
+ $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
+ }
+
+ if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
+ $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
+ }
+
+ if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
+ $queryKeyValues = explode('&', $uri->getQuery());
+ sort($queryKeyValues);
+ $uri = $uri->withQuery(implode('&', $queryKeyValues));
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether two URIs can be considered equivalent.
+ *
+ * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
+ * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
+ * resolved against the same base URI. If this is not the case, determination of equivalence or difference of
+ * relative references does not mean anything.
+ *
+ * @param UriInterface $uri1 An URI to compare
+ * @param UriInterface $uri2 An URI to compare
+ * @param int $normalizations A bitmask of normalizations to apply, see constants
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-6.1
+ */
+ public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
+ {
+ return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
+ }
+
+ private static function capitalizePercentEncoding(UriInterface $uri)
+ {
+ $regex = '/(?:%[A-Fa-f0-9]{2})++/';
+
+ $callback = function (array $match) {
+ return strtoupper($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private static function decodeUnreservedCharacters(UriInterface $uri)
+ {
+ $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
+
+ $callback = function (array $match) {
+ return rawurldecode($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/UriResolver.php b/assets/php/vendor/guzzlehttp/psr7/src/UriResolver.php
new file mode 100644
index 0000000..c1cb8a2
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/UriResolver.php
@@ -0,0 +1,219 @@
+getScheme() != '') {
+ return $rel->withPath(self::removeDotSegments($rel->getPath()));
+ }
+
+ if ($rel->getAuthority() != '') {
+ $targetAuthority = $rel->getAuthority();
+ $targetPath = self::removeDotSegments($rel->getPath());
+ $targetQuery = $rel->getQuery();
+ } else {
+ $targetAuthority = $base->getAuthority();
+ if ($rel->getPath() === '') {
+ $targetPath = $base->getPath();
+ $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
+ } else {
+ if ($rel->getPath()[0] === '/') {
+ $targetPath = $rel->getPath();
+ } else {
+ if ($targetAuthority != '' && $base->getPath() === '') {
+ $targetPath = '/' . $rel->getPath();
+ } else {
+ $lastSlashPos = strrpos($base->getPath(), '/');
+ if ($lastSlashPos === false) {
+ $targetPath = $rel->getPath();
+ } else {
+ $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
+ }
+ }
+ }
+ $targetPath = self::removeDotSegments($targetPath);
+ $targetQuery = $rel->getQuery();
+ }
+ }
+
+ return new Uri(Uri::composeComponents(
+ $base->getScheme(),
+ $targetAuthority,
+ $targetPath,
+ $targetQuery,
+ $rel->getFragment()
+ ));
+ }
+
+ /**
+ * Returns the target URI as a relative reference from the base URI.
+ *
+ * This method is the counterpart to resolve():
+ *
+ * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+ *
+ * One use-case is to use the current request URI as base URI and then generate relative links in your documents
+ * to reduce the document size or offer self-contained downloadable document archives.
+ *
+ * $base = new Uri('http://example.com/a/b/');
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+ * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+ *
+ * This method also accepts a target that is already relative and will try to relativize it further. Only a
+ * relative-path reference will be returned as-is.
+ *
+ * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
+ *
+ * @param UriInterface $base Base URI
+ * @param UriInterface $target Target URI
+ *
+ * @return UriInterface The relative URI reference
+ */
+ public static function relativize(UriInterface $base, UriInterface $target)
+ {
+ if ($target->getScheme() !== '' &&
+ ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
+ ) {
+ return $target;
+ }
+
+ if (Uri::isRelativePathReference($target)) {
+ // As the target is already highly relative we return it as-is. It would be possible to resolve
+ // the target with `$target = self::resolve($base, $target);` and then try make it more relative
+ // by removing a duplicate query. But let's not do that automatically.
+ return $target;
+ }
+
+ if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
+ return $target->withScheme('');
+ }
+
+ // We must remove the path before removing the authority because if the path starts with two slashes, the URI
+ // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
+ // invalid.
+ $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
+
+ if ($base->getPath() !== $target->getPath()) {
+ return $emptyPathUri->withPath(self::getRelativePath($base, $target));
+ }
+
+ if ($base->getQuery() === $target->getQuery()) {
+ // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
+ return $emptyPathUri->withQuery('');
+ }
+
+ // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
+ // inherit the base query component when resolving.
+ if ($target->getQuery() === '') {
+ $segments = explode('/', $target->getPath());
+ $lastSegment = end($segments);
+
+ return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
+ }
+
+ return $emptyPathUri;
+ }
+
+ private static function getRelativePath(UriInterface $base, UriInterface $target)
+ {
+ $sourceSegments = explode('/', $base->getPath());
+ $targetSegments = explode('/', $target->getPath());
+ array_pop($sourceSegments);
+ $targetLastSegment = array_pop($targetSegments);
+ foreach ($sourceSegments as $i => $segment) {
+ if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
+ unset($sourceSegments[$i], $targetSegments[$i]);
+ } else {
+ break;
+ }
+ }
+ $targetSegments[] = $targetLastSegment;
+ $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
+
+ // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
+ if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
+ $relativePath = "./$relativePath";
+ } elseif ('/' === $relativePath[0]) {
+ if ($base->getAuthority() != '' && $base->getPath() === '') {
+ // In this case an extra slash is added by resolve() automatically. So we must not add one here.
+ $relativePath = ".$relativePath";
+ } else {
+ $relativePath = "./$relativePath";
+ }
+ }
+
+ return $relativePath;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/functions.php b/assets/php/vendor/guzzlehttp/psr7/src/functions.php
new file mode 100644
index 0000000..e40348d
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/functions.php
@@ -0,0 +1,828 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
+ * returns a UriInterface for the given value. If the value is already a
+ * `UriInterface`, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ * @throws \InvalidArgumentException
+ */
+function uri_for($uri)
+{
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ } elseif (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+}
+
+/**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return Stream
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+function stream_for($resource = '', array $options = [])
+{
+ if (is_scalar($resource)) {
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ }
+
+ switch (gettype($resource)) {
+ case 'resource':
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return stream_for((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(fopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = [];
+
+ foreach (normalize_header($header) as $val) {
+ $part = [];
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = [];
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+
+ if ($port = $changes['uri']->getPort()) {
+ $standardPorts = ['http' => 80, 'https' => 443];
+ $scheme = $changes['uri']->getScheme();
+ if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
+ $changes['set_headers']['Host'] .= ':'.$port;
+ }
+ }
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = _caseless_remove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ if ($request instanceof ServerRequestInterface) {
+ return new ServerRequest(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion(),
+ $request->getServerParams()
+ );
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()` returns a
+ * value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+}
+
+/**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ * @throws \RuntimeException if the file cannot be opened
+ */
+function try_fopen($filename, $mode)
+{
+ $ex = null;
+ set_error_handler(function () use ($filename, $mode, &$ex) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open %s using mode %s: %s',
+ $filename,
+ $mode,
+ func_get_args()[1]
+ ));
+ });
+
+ $handle = fopen($filename, $mode);
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+}
+
+/**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ * @return string
+ * @throws \RuntimeException on error.
+ */
+function copy_to_string(StreamInterface $stream, $maxLen = -1)
+{
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+}
+
+/**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+function copy_to_stream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+) {
+ $bufferSize = 8192;
+
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read($bufferSize))) {
+ break;
+ }
+ }
+ } else {
+ $remaining = $maxLen;
+ while ($remaining > 0 && !$source->eof()) {
+ $buf = $source->read(min($bufferSize, $remaining));
+ $len = strlen($buf);
+ if (!$len) {
+ break;
+ }
+ $remaining -= $len;
+ $dest->write($buf);
+ }
+ }
+}
+
+/**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws \RuntimeException on error.
+ */
+function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+) {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+}
+
+/**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string|bool
+ */
+function readline(StreamInterface $stream, $maxLength = null)
+{
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte === "\n" || ++$size === $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+}
+
+/**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+function parse_request($message)
+{
+ $data = _parse_message($message);
+ $matches = [];
+ if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+}
+
+/**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+function parse_response($message)
+{
+ $data = _parse_message($message);
+ // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
+ // between status-code and reason-phrase is required. But browsers accept
+ // responses without space and reason as well.
+ if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+
+ return new Response(
+ $parts[1],
+ $data['headers'],
+ $data['body'],
+ explode('/', $parts[0])[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+}
+
+/**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
+ * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
+ *
+ * @param string $str Query string to parse
+ * @param bool|string $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+function parse_query($str, $urlEncoding = true)
+{
+ $result = [];
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding == PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding == PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) { return $str; };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = [$result[$key]];
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of parse_query() to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like http_build_query would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ * @return string
+ */
+function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
+{
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) { return $str; };
+ } elseif ($encoding === PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding === PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+}
+
+/**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @param $filename
+ *
+ * @return null|string
+ */
+function mimetype_from_filename($filename)
+{
+ return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
+}
+
+/**
+ * Maps a file extensions to a mimetype.
+ *
+ * @param $extension string The file extension.
+ *
+ * @return string|null
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+function mimetype_from_extension($extension)
+{
+ static $mimetypes = [
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ ];
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+}
+
+/**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ * @internal
+ */
+function _parse_message($message)
+{
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ // Iterate over each line in the message, accounting for line endings
+ $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => ''];
+ array_shift($lines);
+
+ for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
+ $line = $lines[$i];
+ // If two line breaks were encountered, then this is the end of body
+ if (empty($line)) {
+ if ($i < $totalLines - 1) {
+ $result['body'] = implode('', array_slice($lines, $i + 2));
+ }
+ break;
+ }
+ if (strpos($line, ':')) {
+ $parts = explode(':', $line, 2);
+ $key = trim($parts[0]);
+ $value = isset($parts[1]) ? trim($parts[1]) : '';
+ $result['headers'][$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ * @internal
+ */
+function _parse_request_uri($path, array $headers)
+{
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+}
+
+/** @internal */
+function _caseless_remove($keys, array $data)
+{
+ $result = [];
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+}
diff --git a/assets/php/vendor/guzzlehttp/psr7/src/functions_include.php b/assets/php/vendor/guzzlehttp/psr7/src/functions_include.php
new file mode 100644
index 0000000..96a4a83
--- /dev/null
+++ b/assets/php/vendor/guzzlehttp/psr7/src/functions_include.php
@@ -0,0 +1,6 @@
+
+
+CMD ["./build.php"]
diff --git a/assets/php/vendor/nubs/random-name-generator/LICENSE b/assets/php/vendor/nubs/random-name-generator/LICENSE
new file mode 100644
index 0000000..4efa9e9
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2016 Spencer Rinehart
+
+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.
diff --git a/assets/php/vendor/nubs/random-name-generator/README.md b/assets/php/vendor/nubs/random-name-generator/README.md
new file mode 100644
index 0000000..70d76cf
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/README.md
@@ -0,0 +1,91 @@
+# Random Name Generator
+A PHP library to create interesting, sometimes entertaining, random names.
+
+[](https://travis-ci.org/nubs/random-name-generator)
+[](https://scrutinizer-ci.com/g/nubs/random-name-generator/)
+[](https://coveralls.io/github/nubs/random-name-generator?branch=master)
+
+[](https://packagist.org/packages/nubs/random-name-generator)
+[](https://packagist.org/packages/nubs/random-name-generator)
+[](https://packagist.org/packages/nubs/random-name-generator)
+
+[](https://www.versioneye.com/user/projects/537d561814c15855aa000019)
+
+## Requirements
+This library requires PHP 5.6, or newer.
+
+## Installation
+This package uses [composer](https://getcomposer.org) so you can just add
+`nubs/random-name-generator` as a dependency to your `composer.json` file or
+execute the following command:
+
+```bash
+composer require nubs/random-name-generator
+```
+
+## Generators
+
+### All
+The "all" generator will utilize all other configured generators to generate
+random names. It will select from the list of generators randomly and then
+use them to generate a random name using their functionality.
+
+#### Usage
+```php
+$generator = \Nubs\RandomNameGenerator\All::create();
+echo $generator->getName();
+```
+
+Alternatively, if you want to configure/build the generators to use instead of
+using all of the available generators, you can construct them yourself:
+
+```php
+$generator = new \Nubs\RandomNameGenerator\All(
+ [
+ new \Nubs\RandomNameGenerator\Alliteration(),
+ new \Nubs\RandomNameGenerator\Vgng()
+ ]
+);
+```
+
+### Video Game Names
+The video game name generator is based off of [prior](http://videogamena.me/)
+[art](https://github.com/nullpuppy/vgng). It will generate unique names based
+off of "typical" video games.
+
+#### Examples
+* *Kamikaze Bubblegum Warrior*
+* *Rockin' Valkyrie Gaiden*
+* *Neurotic Jackhammer Detective*
+* *My Little Mountain Climber Conflict*
+* *Small-Time Princess vs. The Space Mutants*
+
+You can also use [this web example](http://sam.sbat.com/) to see more example
+video game names generated by this library.
+
+#### Usage
+```php
+$generator = new \Nubs\RandomNameGenerator\Vgng();
+echo $generator->getName();
+```
+
+## Alliterative Names
+The alliteration name generator is based off of a list of
+[adjectives](http://grammar.yourdictionary.com/parts-of-speech/adjectives/list-of-adjective-words.html)
+and a list of [animals](https://animalcorner.co.uk/animals/).
+
+#### Examples
+* *Agreeable Anaconda*
+* *Disturbed Duck*
+* *Misty Meerkat*
+* *Prickly Pig*
+
+#### Usage
+```php
+$generator = new \Nubs\RandomNameGenerator\Alliteration();
+echo $generator->getName();
+```
+
+## License
+random-name-generator is licensed under the MIT license. See
+[LICENSE](LICENSE) for the full license text.
diff --git a/assets/php/vendor/nubs/random-name-generator/build.php b/assets/php/vendor/nubs/random-name-generator/build.php
new file mode 100755
index 0000000..f285d55
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/build.php
@@ -0,0 +1,25 @@
+#!/usr/bin/env php
+process(['standard' => ['PSR1'], 'files' => ['src', 'tests', 'build.php']]);
+if ($phpcsViolations > 0) {
+ exit(1);
+}
+
+$phpunitConfiguration = PHPUnit_Util_Configuration::getInstance(__DIR__ . '/phpunit.xml');
+$phpunitArguments = ['coverageHtml' => __DIR__ . '/coverage', 'configuration' => $phpunitConfiguration];
+$testRunner = new PHPUnit_TextUI_TestRunner();
+$result = $testRunner->doRun($phpunitConfiguration->getTestSuiteConfiguration(), $phpunitArguments, false);
+if (!$result->wasSuccessful()) {
+ exit(1);
+}
+
+$coverageReport = $result->getCodeCoverage()->getReport();
+if ($coverageReport->getNumExecutedLines() !== $coverageReport->getNumExecutableLines()) {
+ file_put_contents('php://stderr', "Code coverage was NOT 100%\n");
+ exit(1);
+}
+
+file_put_contents('php://stderr', "Code coverage was 100%\n");
diff --git a/assets/php/vendor/nubs/random-name-generator/composer.json b/assets/php/vendor/nubs/random-name-generator/composer.json
new file mode 100644
index 0000000..d91fbff
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "nubs/random-name-generator",
+ "description": "A library to create interesting, sometimes entertaining, random names.",
+ "keywords": ["random", "generator", "video game", "alliteration"],
+ "authors": [
+ {
+ "name": "Spencer Rinehart",
+ "email": "anubis@overthemonkey.com",
+ "role": "Developer"
+ }
+ ],
+ "license": "MIT",
+ "require": {
+ "php": "~5.6 || ~7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5.0",
+ "satooshi/php-coveralls": "~1.0",
+ "cinam/randomizer": ">=1.1.1,<2.0",
+ "squizlabs/php_codesniffer": "~2.3"
+ },
+ "autoload": {
+ "psr-4": {
+ "Nubs\\RandomNameGenerator\\": "src"
+ }
+ },
+ "scripts": {
+ "test": "./build.php"
+ }
+}
diff --git a/assets/php/vendor/nubs/random-name-generator/composer.lock b/assets/php/vendor/nubs/random-name-generator/composer.lock
new file mode 100644
index 0000000..77d0fdb
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/composer.lock
@@ -0,0 +1,1963 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "4b2c771eec058567f987575b9b3199a2",
+ "content-hash": "826850e9b398ef46a5bc5fec486788f6",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "cinam/randomizer",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cinam/randomizer.git",
+ "reference": "beca7e3ad5b93cebdb897cd47247b19a109b970f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cinam/randomizer/zipball/beca7e3ad5b93cebdb897cd47247b19a109b970f",
+ "reference": "beca7e3ad5b93cebdb897cd47247b19a109b970f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Cinam\\Randomizer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "cinam",
+ "email": "cinam@hotmail.com"
+ }
+ ],
+ "description": "Tools for generating random values.",
+ "homepage": "http://github.com/cinam/randomizer",
+ "keywords": [
+ "random",
+ "random numbers",
+ "random values"
+ ],
+ "time": "2014-06-01 07:27:32"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3,<8.0-DEV"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://github.com/doctrine/instantiator",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2015-06-14 21:17:01"
+ },
+ {
+ "name": "guzzle/guzzle",
+ "version": "v3.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle3.git",
+ "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "php": ">=5.3.3",
+ "symfony/event-dispatcher": "~2.1"
+ },
+ "replace": {
+ "guzzle/batch": "self.version",
+ "guzzle/cache": "self.version",
+ "guzzle/common": "self.version",
+ "guzzle/http": "self.version",
+ "guzzle/inflection": "self.version",
+ "guzzle/iterator": "self.version",
+ "guzzle/log": "self.version",
+ "guzzle/parser": "self.version",
+ "guzzle/plugin": "self.version",
+ "guzzle/plugin-async": "self.version",
+ "guzzle/plugin-backoff": "self.version",
+ "guzzle/plugin-cache": "self.version",
+ "guzzle/plugin-cookie": "self.version",
+ "guzzle/plugin-curlauth": "self.version",
+ "guzzle/plugin-error-response": "self.version",
+ "guzzle/plugin-history": "self.version",
+ "guzzle/plugin-log": "self.version",
+ "guzzle/plugin-md5": "self.version",
+ "guzzle/plugin-mock": "self.version",
+ "guzzle/plugin-oauth": "self.version",
+ "guzzle/service": "self.version",
+ "guzzle/stream": "self.version"
+ },
+ "require-dev": {
+ "doctrine/cache": "~1.3",
+ "monolog/monolog": "~1.0",
+ "phpunit/phpunit": "3.7.*",
+ "psr/log": "~1.0",
+ "symfony/class-loader": "~2.1",
+ "zendframework/zend-cache": "2.*,<2.3",
+ "zendframework/zend-log": "2.*,<2.3"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Guzzle": "src/",
+ "Guzzle\\Tests": "tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Guzzle Community",
+ "homepage": "https://github.com/guzzle/guzzle/contributors"
+ }
+ ],
+ "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ],
+ "abandoned": "guzzlehttp/guzzle",
+ "time": "2015-03-18 18:23:50"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "a8773992b362b58498eed24bf85005f363c34771"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a8773992b362b58498eed24bf85005f363c34771",
+ "reference": "a8773992b362b58498eed24bf85005f363c34771",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "doctrine/collections": "1.*",
+ "phpunit/phpunit": "~4.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "homepage": "https://github.com/myclabs/DeepCopy",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "time": "2015-11-20 12:04:31"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+ "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2015-12-27 11:43:31"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "9270140b940ff02e58ec577c237274e92cd40cdd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd",
+ "reference": "9270140b940ff02e58ec577c237274e92cd40cdd",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5",
+ "phpdocumentor/reflection-common": "^1.0@dev",
+ "phpdocumentor/type-resolver": "^0.2.0",
+ "webmozart/assert": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^4.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2016-06-10 09:48:41"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443",
+ "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5",
+ "phpdocumentor/reflection-common": "^1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^0.9.4",
+ "phpunit/phpunit": "^5.2||^4.8.24"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "time": "2016-06-10 07:14:17"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "58a8137754bc24b25740d4281399a4a3596058e0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0",
+ "reference": "58a8137754bc24b25740d4281399a4a3596058e0",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
+ "sebastian/comparator": "^1.1",
+ "sebastian/recursion-context": "^1.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Prophecy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2016-06-07 08:13:47"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3",
+ "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0",
+ "phpunit/php-file-iterator": "~1.3",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-token-stream": "^1.4.2",
+ "sebastian/code-unit-reverse-lookup": "~1.0",
+ "sebastian/environment": "^1.3.2 || ^2.0",
+ "sebastian/version": "~1.0|~2.0"
+ },
+ "require-dev": {
+ "ext-xdebug": ">=2.1.4",
+ "phpunit/phpunit": "^5.4"
+ },
+ "suggest": {
+ "ext-dom": "*",
+ "ext-xdebug": ">=2.4.0",
+ "ext-xmlwriter": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2016-07-26 14:39:29"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+ "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2015-06-21 13:08:43"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2015-06-21 13:50:34"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4|~5"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2016-05-12 18:03:57"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "1.4.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+ "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "time": "2015-09-15 10:49:45"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "5.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "46ec2d1522ae8c9a12aca6b7650e0be78bbb0502"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46ec2d1522ae8c9a12aca6b7650e0be78bbb0502",
+ "reference": "46ec2d1522ae8c9a12aca6b7650e0be78bbb0502",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "myclabs/deep-copy": "~1.3",
+ "php": "^5.6 || ^7.0",
+ "phpspec/prophecy": "^1.3.1",
+ "phpunit/php-code-coverage": "^4.0.1",
+ "phpunit/php-file-iterator": "~1.4",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-timer": "^1.0.6",
+ "phpunit/phpunit-mock-objects": "^3.2",
+ "sebastian/comparator": "~1.1",
+ "sebastian/diff": "~1.2",
+ "sebastian/environment": "^1.3 || ^2.0",
+ "sebastian/exporter": "~1.2",
+ "sebastian/global-state": "~1.0",
+ "sebastian/object-enumerator": "~1.0",
+ "sebastian/resource-operations": "~1.0",
+ "sebastian/version": "~1.0|~2.0",
+ "symfony/yaml": "~2.1|~3.0"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "3.0.2"
+ },
+ "suggest": {
+ "phpunit/php-invoker": "~1.1"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.5.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2016-08-18 11:10:44"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "3.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "4e83390f64e7ce04fcaec2ce95cd72823b431d19"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/4e83390f64e7ce04fcaec2ce95cd72823b431d19",
+ "reference": "4e83390f64e7ce04fcaec2ce95cd72823b431d19",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.6 || ^7.0",
+ "phpunit/php-text-template": "^1.2",
+ "sebastian/exporter": "^1.2"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.4"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ],
+ "time": "2016-08-17 09:33:51"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "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",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "time": "2012-12-21 11:40:51"
+ },
+ {
+ "name": "satooshi/php-coveralls",
+ "version": "v1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/satooshi/php-coveralls.git",
+ "reference": "da51d304fe8622bf9a6da39a8446e7afd432115c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/da51d304fe8622bf9a6da39a8446e7afd432115c",
+ "reference": "da51d304fe8622bf9a6da39a8446e7afd432115c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-simplexml": "*",
+ "guzzle/guzzle": "^2.8|^3.0",
+ "php": ">=5.3.3",
+ "psr/log": "^1.0",
+ "symfony/config": "^2.1|^3.0",
+ "symfony/console": "^2.1|^3.0",
+ "symfony/stopwatch": "^2.0|^3.0",
+ "symfony/yaml": "^2.0|^3.0"
+ },
+ "suggest": {
+ "symfony/http-kernel": "Allows Symfony integration"
+ },
+ "bin": [
+ "bin/coveralls"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Satooshi\\": "src/Satooshi/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kitamura Satoshi",
+ "email": "with.no.parachute@gmail.com",
+ "homepage": "https://www.facebook.com/satooshi.jp"
+ }
+ ],
+ "description": "PHP client library for Coveralls API",
+ "homepage": "https://github.com/satooshi/php-coveralls",
+ "keywords": [
+ "ci",
+ "coverage",
+ "github",
+ "test"
+ ],
+ "time": "2016-01-20 17:35:46"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe",
+ "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "time": "2016-02-13 06:45:14"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
+ "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/diff": "~1.2",
+ "sebastian/exporter": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "time": "2015-07-26 15:48:44"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff"
+ ],
+ "time": "2015-12-08 07:14:41"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "1.3.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+ "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8 || ^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "time": "2016-08-18 05:49:44"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
+ "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2016-06-17 09:04:28"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2015-10-12 03:26:01"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "d4ca2fb70344987502567bc50081c03e6192fb26"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26",
+ "reference": "d4ca2fb70344987502567bc50081c03e6192fb26",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "time": "2016-01-28 13:25:10"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
+ "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2015-11-11 19:50:13"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "time": "2015-07-28 20:34:47"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5",
+ "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "time": "2016-02-04 12:56:52"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "2.6.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "4edb770cb853def6e60c93abb088ad5ac2010c83"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/4edb770cb853def6e60c93abb088ad5ac2010c83",
+ "reference": "4edb770cb853def6e60c93abb088ad5ac2010c83",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "bin": [
+ "scripts/phpcs",
+ "scripts/phpcbf"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "CodeSniffer.php",
+ "CodeSniffer/CLI.php",
+ "CodeSniffer/Exception.php",
+ "CodeSniffer/File.php",
+ "CodeSniffer/Fixer.php",
+ "CodeSniffer/Report.php",
+ "CodeSniffer/Reporting.php",
+ "CodeSniffer/Sniff.php",
+ "CodeSniffer/Tokens.php",
+ "CodeSniffer/Reports/",
+ "CodeSniffer/Tokenizers/",
+ "CodeSniffer/DocGenerators/",
+ "CodeSniffer/Standards/AbstractPatternSniff.php",
+ "CodeSniffer/Standards/AbstractScopeSniff.php",
+ "CodeSniffer/Standards/AbstractVariableSniff.php",
+ "CodeSniffer/Standards/IncorrectPatternException.php",
+ "CodeSniffer/Standards/Generic/Sniffs/",
+ "CodeSniffer/Standards/MySource/Sniffs/",
+ "CodeSniffer/Standards/PEAR/Sniffs/",
+ "CodeSniffer/Standards/PSR1/Sniffs/",
+ "CodeSniffer/Standards/PSR2/Sniffs/",
+ "CodeSniffer/Standards/Squiz/Sniffs/",
+ "CodeSniffer/Standards/Zend/Sniffs/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "lead"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "http://www.squizlabs.com/php-codesniffer",
+ "keywords": [
+ "phpcs",
+ "standards"
+ ],
+ "time": "2016-07-13 23:29:13"
+ },
+ {
+ "name": "symfony/config",
+ "version": "v3.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/config.git",
+ "reference": "a7630397b91be09cdd2fe57fd13612e258700598"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/config/zipball/a7630397b91be09cdd2fe57fd13612e258700598",
+ "reference": "a7630397b91be09cdd2fe57fd13612e258700598",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/filesystem": "~2.8|~3.0"
+ },
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Config\\": ""
+ },
+ "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 Config Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-07-26 08:04:17"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v3.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "f9e638e8149e9e41b570ff092f8007c477ef0ce5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/f9e638e8149e9e41b570ff092f8007c477ef0ce5",
+ "reference": "f9e638e8149e9e41b570ff092f8007c477ef0ce5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/event-dispatcher": "~2.8|~3.0",
+ "symfony/process": "~2.8|~3.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "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 Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-07-26 08:04:17"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v2.8.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8",
+ "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.0,>=2.0.5|~3.0.0",
+ "symfony/dependency-injection": "~2.6|~3.0.0",
+ "symfony/expression-language": "~2.6|~3.0.0",
+ "symfony/stopwatch": "~2.3|~3.0.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "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 EventDispatcher Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-07-28 16:56:28"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v3.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "bb29adceb552d202b6416ede373529338136e84f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/bb29adceb552d202b6416ede373529338136e84f",
+ "reference": "bb29adceb552d202b6416ede373529338136e84f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "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",
+ "time": "2016-07-20 05:44:26"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "dff51f72b0706335131b00a7f49606168c582594"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594",
+ "reference": "dff51f72b0706335131b00a7f49606168c582594",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "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"
+ ],
+ "time": "2016-05-18 14:26:46"
+ },
+ {
+ "name": "symfony/stopwatch",
+ "version": "v3.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/stopwatch.git",
+ "reference": "bb42806b12c5f89db4ebf64af6741afe6d8457e1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/stopwatch/zipball/bb42806b12c5f89db4ebf64af6741afe6d8457e1",
+ "reference": "bb42806b12c5f89db4ebf64af6741afe6d8457e1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Stopwatch\\": ""
+ },
+ "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 Stopwatch Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-06-29 05:41:56"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v3.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "1819adf2066880c7967df7180f4f662b6f0567ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/1819adf2066880c7967df7180f4f662b6f0567ac",
+ "reference": "1819adf2066880c7967df7180f4f662b6f0567ac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "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 Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-07-17 14:02:08"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "bb2d123231c095735130cc8f6d31385a44c7b308"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308",
+ "reference": "bb2d123231c095735130cc8f6d31385a44c7b308",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3|^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.6",
+ "sebastian/version": "^1.0.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "time": "2016-08-09 15:02:57"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "~5.6 || ~7.0"
+ },
+ "platform-dev": []
+}
diff --git a/assets/php/vendor/nubs/random-name-generator/docker-compose.yml b/assets/php/vendor/nubs/random-name-generator/docker-compose.yml
new file mode 100644
index 0000000..e52fe45
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/docker-compose.yml
@@ -0,0 +1,8 @@
+version: "2"
+services:
+ build:
+ build:
+ context: .
+ dockerfile: Dockerfile.tests
+ volumes:
+ - .:/code
diff --git a/assets/php/vendor/nubs/random-name-generator/phpunit.xml b/assets/php/vendor/nubs/random-name-generator/phpunit.xml
new file mode 100644
index 0000000..dc845da
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/phpunit.xml
@@ -0,0 +1,10 @@
+
+
+ ./tests
+
+
+
+ src
+
+
+
diff --git a/assets/php/vendor/nubs/random-name-generator/src/AbstractGenerator.php b/assets/php/vendor/nubs/random-name-generator/src/AbstractGenerator.php
new file mode 100644
index 0000000..abfae12
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/src/AbstractGenerator.php
@@ -0,0 +1,19 @@
+getName();
+ }
+}
diff --git a/assets/php/vendor/nubs/random-name-generator/src/All.php b/assets/php/vendor/nubs/random-name-generator/src/All.php
new file mode 100644
index 0000000..d044c74
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/src/All.php
@@ -0,0 +1,62 @@
+_generators = $generators;
+ $this->_randomizer = $randomizer;
+ }
+
+ /**
+ * Constructs an All Generator using the default list of generators.
+ *
+ * @api
+ * @param \Cinam\Randomizer\Randomizer $randomizer The random number generator.
+ * @return \Nubs\RandomNameGenerator\All The constructed generator.
+ */
+ public static function create(Randomizer $randomizer = null)
+ {
+ return new self([new Alliteration($randomizer), new Vgng($randomizer)], $randomizer);
+ }
+
+ /**
+ * Gets a randomly generated name using the configured generators.
+ *
+ * @api
+ * @return string A random name.
+ */
+ public function getName()
+ {
+ return $this->_getRandomGenerator()->getName();
+ }
+
+ /**
+ * Get a random generator from the list of generators.
+ *
+ * @return \Nubs\RandomNameGenerator\Generator A random generator.
+ */
+ protected function _getRandomGenerator()
+ {
+ return $this->_randomizer ? $this->_randomizer->getArrayValue($this->_generators) : $this->_generators[array_rand($this->_generators)];
+ }
+}
diff --git a/assets/php/vendor/nubs/random-name-generator/src/Alliteration.php b/assets/php/vendor/nubs/random-name-generator/src/Alliteration.php
new file mode 100644
index 0000000..68ef3a2
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/src/Alliteration.php
@@ -0,0 +1,59 @@
+_randomizer = $randomizer;
+ $this->_adjectives = file(__DIR__ . '/adjectives.txt', FILE_IGNORE_NEW_LINES);
+ $this->_nouns = file(__DIR__ . '/nouns.txt', FILE_IGNORE_NEW_LINES);
+ }
+
+ /**
+ * Gets a randomly generated alliterative name.
+ *
+ * @api
+ * @return string A random alliterative name.
+ */
+ public function getName()
+ {
+ $adjective = $this->_getRandomWord($this->_adjectives);
+ $noun = $this->_getRandomWord($this->_nouns, $adjective[0]);
+
+ return ucwords("{$adjective} {$noun}");
+ }
+
+ /**
+ * Get a random word from the list of words, optionally filtering by starting letter.
+ *
+ * @param array $words An array of words to choose from.
+ * @param string $startingLetter The desired starting letter of the word.
+ * @return string The random word.
+ */
+ protected function _getRandomWord(array $words, $startingLetter = null)
+ {
+ $wordsToSearch = $startingLetter === null ? $words : preg_grep("/^{$startingLetter}/", $words);
+ return $this->_randomizer ? $this->_randomizer->getArrayValue($wordsToSearch) : $wordsToSearch[array_rand($wordsToSearch)];
+ }
+}
diff --git a/assets/php/vendor/nubs/random-name-generator/src/Generator.php b/assets/php/vendor/nubs/random-name-generator/src/Generator.php
new file mode 100644
index 0000000..572c990
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/src/Generator.php
@@ -0,0 +1,16 @@
+_randomizer = $randomizer;
+ $this->_definitionSets = array_map(
+ [$this, '_parseSection'],
+ $this->_getSections($this->_getFileContents())
+ );
+ }
+
+ /**
+ * Gets a randomly generated video game name.
+ *
+ * @api
+ * @return string A random video game name.
+ */
+ public function getName()
+ {
+ $similarWords = [];
+ $words = [];
+
+ foreach ($this->_definitionSets as $definitionSet) {
+ $word = $this->_getUniqueWord($definitionSet, $similarWords);
+ $words[] = $word['word'];
+ $similarWords[] = $word['word'];
+ $similarWords = array_merge($similarWords, $word['similarWords']);
+ }
+
+ return implode(' ', $words);
+ }
+
+ /**
+ * Get a definition from the definitions that does not exist already.
+ *
+ * @param array $definitions The word list to pick from.
+ * @param array $existingWords The already-chosen words to avoid.
+ * @return array The definition of a previously unchosen word.
+ */
+ protected function _getUniqueWord(array $definitions, array $existingWords)
+ {
+ $definition = $this->_randomizer ? $this->_randomizer->getArrayValue($definitions) : $definitions[array_rand($definitions)];
+
+ if (array_search($definition['word'], $existingWords) === false) {
+ return $definition;
+ }
+
+ return $this->_getUniqueWord($definitions, $existingWords);
+ }
+
+ /**
+ * Gets the file contents of the video_game_names.txt file.
+ *
+ * @return string The video_game_names.txt contents.
+ */
+ protected function _getFileContents()
+ {
+ return file_get_contents(__DIR__ . '/video_game_names.txt');
+ }
+
+ /**
+ * Separates the contents into each of the word list sections.
+ *
+ * This builder operates by picking a random word from each of a consecutive
+ * list of word lists. These separate lists are separated by a line
+ * consisting of four hyphens in the file.
+ *
+ * @param string $contents The file contents.
+ * @return array Each section split into its own string.
+ */
+ protected function _getSections($contents)
+ {
+ return array_map('trim', explode('----', $contents));
+ }
+
+ /**
+ * Parses the given section into the final definitions.
+ *
+ * @param string $section The newline-separated list of words in a section.
+ * @return array The parsed section into its final form.
+ */
+ protected function _parseSection($section)
+ {
+ return array_map(
+ [$this, '_parseDefinition'],
+ $this->_getDefinitionsFromSection($section)
+ );
+ }
+
+ /**
+ * Gets the separate definitions from the given section.
+ *
+ * @param string $section The newline-separated list of words in a section.
+ * @return array Each word split out, but unparsed.
+ */
+ protected function _getDefinitionsFromSection($section)
+ {
+ return array_map('trim', explode("\n", $section));
+ }
+
+ /**
+ * Parses a single definition into its component pieces.
+ *
+ * The format of a definition in a file is word[^similarWord|...].
+ *
+ * @param string $definition The definition.
+ * @return array The formatted definition.
+ */
+ protected function _parseDefinition($definition)
+ {
+ $word = strtok($definition, '^');
+ $similarWords = array_filter(explode('|', strtok('^')));
+
+ return ['word' => $word, 'similarWords' => $similarWords];
+ }
+}
diff --git a/assets/php/vendor/nubs/random-name-generator/src/adjectives.txt b/assets/php/vendor/nubs/random-name-generator/src/adjectives.txt
new file mode 100644
index 0000000..f8d3247
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/src/adjectives.txt
@@ -0,0 +1,233 @@
+adorable
+adventurous
+aggressive
+agreeable
+alert
+alive
+amused
+angry
+annoyed
+annoying
+anxious
+arrogant
+ashamed
+attractive
+average
+awful
+bad
+beautiful
+better
+bewildered
+black
+bloody
+blue
+blue-eyed
+blushing
+bored
+brainy
+brave
+breakable
+bright
+busy
+calm
+careful
+cautious
+charming
+cheerful
+clean
+clear
+clever
+cloudy
+clumsy
+colorful
+combative
+comfortable
+concerned
+condemned
+confused
+cooperative
+courageous
+crazy
+creepy
+crowded
+cruel
+curious
+cute
+dangerous
+dark
+dead
+defeated
+defiant
+delightful
+depressed
+determined
+different
+difficult
+disgusted
+distinct
+disturbed
+dizzy
+doubtful
+drab
+dull
+eager
+easy
+elated
+elegant
+embarrassed
+enchanting
+encouraging
+energetic
+enthusiastic
+envious
+evil
+excited
+expensive
+exuberant
+fair
+faithful
+famous
+fancy
+fantastic
+fierce
+filthy
+fine
+foolish
+fragile
+frail
+frantic
+friendly
+frightened
+funny
+gentle
+gifted
+glamorous
+gleaming
+glorious
+good
+gorgeous
+graceful
+grieving
+grotesque
+grumpy
+handsome
+happy
+healthy
+helpful
+helpless
+hilarious
+homeless
+homely
+horrible
+hungry
+hurt
+ill
+important
+impossible
+inexpensive
+innocent
+inquisitive
+itchy
+jealous
+jittery
+jolly
+joyous
+kind
+lazy
+light
+lively
+lonely
+long
+lovely
+lucky
+magnificent
+misty
+modern
+motionless
+muddy
+mushy
+mysterious
+nasty
+naughty
+nervous
+nice
+nutty
+obedient
+obnoxious
+odd
+old-fashioned
+open
+outrageous
+outstanding
+panicky
+perfect
+plain
+pleasant
+poised
+poor
+powerful
+precious
+prickly
+proud
+puzzled
+quaint
+real
+relieved
+repulsive
+rich
+scary
+selfish
+shiny
+shy
+silly
+sleepy
+smiling
+smoggy
+sore
+sparkling
+splendid
+spotless
+stormy
+strange
+stupid
+successful
+super
+talented
+tame
+tender
+tense
+terrible
+testy
+thankful
+thoughtful
+thoughtless
+tired
+tough
+troubled
+ugliest
+ugly
+uninterested
+unsightly
+unusual
+upset
+uptight
+vast
+victorious
+vivacious
+wandering
+weary
+wicked
+wide-eyed
+wild
+witty
+worrisome
+worried
+wrong
+xenophobic
+xanthous
+xerothermic
+yawning
+yellowed
+yucky
+zany
+zealous
diff --git a/assets/php/vendor/nubs/random-name-generator/src/nouns.txt b/assets/php/vendor/nubs/random-name-generator/src/nouns.txt
new file mode 100644
index 0000000..4e4adc2
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/src/nouns.txt
@@ -0,0 +1,313 @@
+aardvark
+addax
+albatross
+alligator
+alpaca
+anaconda
+angelfish
+anteater
+antelope
+ant
+ape
+armadillo
+baboon
+badger
+barracuda
+bat
+batfish
+bear
+beaver
+bee
+beetle
+bird
+bison
+boar
+booby
+buffalo
+bug
+butterfly
+buzzard
+caiman
+camel
+capuchin
+capybara
+caracal
+cardinal
+caribou
+cassowary
+cat
+caterpillar
+centipede
+chamois
+cheetah
+chicken
+chimpanzee
+chinchilla
+chipmunk
+cicada
+civet
+cobra
+cockroach
+cod
+constrictor
+copperhead
+cormorant
+corncrake
+cottonmouth
+cowfish
+cow
+coyote
+crab
+crane
+crayfish
+crocodile
+crossbill
+curlew
+deer
+dingo
+dog
+dogfish
+dolphin
+donkey
+dormouse
+dotterel
+dove
+dragonfly
+duck
+dugong
+dunlin
+eagle
+earthworm
+echidna
+eel
+eland
+elephant
+elk
+emu
+falcon
+ferret
+finch
+fish
+flamingo
+flatworm
+fly
+fowl
+fox
+frog
+gannet
+gaur
+gazelle
+gecko
+gemsbok
+gentoo
+gerbil
+gerenuk
+gharial
+gibbon
+giraffe
+gnat
+gnu
+goat
+goldfinch
+goosander
+goose
+gorilla
+goshawk
+grasshopper
+grebe
+grivet
+grouse
+guanaco
+gull
+hamerkop
+hamster
+hare
+hawk
+hedgehog
+heron
+herring
+hippopotamus
+hoopoe
+hornet
+horse
+hummingbird
+hyena
+ibex
+ibis
+iguana
+impala
+jackal
+jaguar
+jay
+jellyfish
+kangaroo
+katipo
+kea
+kestrel
+kingfisher
+kinkajou
+kitten
+koala
+kookaburra
+kouprey
+kudu
+ladybird
+lapwing
+lark
+lemur
+leopard
+lion
+lizard
+llama
+lobster
+locust
+loris
+louse
+lynx
+lyrebird
+macaque
+macaw
+magpie
+mallard
+mamba
+manatee
+mandrill
+mantis
+manx
+markhor
+marten
+meerkat
+millipede
+mink
+mockingbird
+mole
+mongoose
+monkey
+moose
+mosquito
+moth
+mouse
+narwhal
+newt
+nightingale
+ocelot
+octopus
+okapi
+opossum
+orangutan
+oryx
+osprey
+ostrich
+otter
+owl
+ox
+oyster
+oystercatcher
+panda
+panther
+parrot
+partridge
+peacock
+peafowl
+peccary
+pelican
+penguin
+petrel
+pheasant
+pig
+pigeon
+pintail
+piranha
+platypus
+plover
+polecat
+pollan
+pony
+porcupine
+porpoise
+puffin
+puma
+pygmy
+quagga
+quail
+quelea
+quetzal
+quoll
+rabbit
+raccoon
+rat
+ratel
+rattlesnake
+raven
+ray
+reindeer
+rhinoceros
+rook
+sable
+salamander
+salmon
+sandpiper
+sardine
+scarab
+seahorse
+seal
+serval
+shark
+sheep
+shrew
+shrike
+skimmer
+skipper
+skunk
+skylark
+sloth
+snail
+snake
+spider
+squirrel
+stag
+starling
+stoat
+stork
+swan
+swiftlet
+tamarin
+tapir
+tarantula
+tarsier
+teira
+termite
+tern
+thrush
+tiger
+toad
+tortoise
+toucan
+trout
+tuatara
+turkey
+turtle
+unicorn
+vendace
+vicuña
+vole
+vulture
+wallaby
+walrus
+warbler
+wasp
+weasel
+weevil
+whale
+wildebeest
+willet
+wolf
+wolverine
+wombat
+worm
+wren
+wryneck
+xenomorph
+yacare
+yak
+zebra
diff --git a/assets/php/vendor/nubs/random-name-generator/src/video_game_names.txt b/assets/php/vendor/nubs/random-name-generator/src/video_game_names.txt
new file mode 100644
index 0000000..a2bcaa2
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/src/video_game_names.txt
@@ -0,0 +1,1276 @@
+3D
+8-Bit
+A Boy and His
+Action
+Advanced^Advance
+Adventures of the^Adventure
+Aero
+African^in Africa
+Alcoholic
+Alien
+Allied
+All-American
+All-Night
+All-Star^Star|Starring Mickey Mouse|Stars|Superstar
+Almighty
+Amateur
+Amazing
+Amazon
+American
+Amish
+Amphibious
+Ancient
+Android^Cyborg
+Angry
+Apathetic
+Aquatic
+Arcane
+Armored
+Art of
+Asian
+Astral
+Attack of the^Attack
+Atomic^Nuclear
+Australian
+Awesome
+Barbie's
+Battle^Battleship|Battalion
+Battlefield:^Battle|Battleship|Battalion
+Beautiful^Beautician
+Bewildering
+Biblical
+Big^Big Game Hunter
+Big Bird's^Big Game Hunter
+Big-Time^Big Game Hunter
+Bionic
+Bizarre
+Bizarro
+Black
+Blasphemous
+Blazing
+Bling Bling
+Blissful
+Blocky
+Bloody^Blood|Bloodbath|of the Blood God
+Bonk's
+Boring
+Bouncin'
+Brain-Damaged
+British
+Britney Spears'
+BudgetSoft Presents:
+Caesar's
+Canadian
+Cantankerous
+Caribbean
+Catholic
+Celebrity
+Celtic
+Charlie Brown's
+Children of the
+Chillin'
+Chinese
+Chocolate
+Christian
+Claustrophobic
+College
+Colonial
+Combat
+Communist
+Confusing
+Cool
+Corporate
+Cosmic
+Crazy
+Create Your Own^Creator
+Creepy
+Cthulhu's
+Curse of the
+Custom
+Cyber
+Cybernetic
+Cyborg^Android
+Dance Dance^Breakdancing|Dance|Dance Mix|Dance Party|Dancers|Square Dancing
+Dangerous
+Darkest
+Day of the
+Dead or Alive^Death|Deathmatch|of Death|of the Dead
+Deadly^Death|Deathmatch|of Death|of the Dead
+Death-Defying^Death|Deathmatch|of Death|of the Dead
+Deep Space^of the Deep
+Def Jam
+Demonic
+Depressing
+Deranged
+Derek Smart's
+Dirty
+Disney's
+Distinguished
+Disturbing
+Divine
+Donkey Kong's
+Double
+Downtown
+Dr.
+Dracula's
+Drug-Induced
+Drunken
+Duke Nukem:
+Dwarven^Dwarf|Gnome|Midget
+Dynamite
+Ebony
+Eco-Friendly
+Educational
+Elderly
+Electric
+Elegant
+Elite
+Elmo's
+Emo
+Endless
+Enormous
+Enraged
+Epic
+Erotic
+Escape from the
+Eternal
+European
+Everybody Hates the
+Everybody Loves the
+Exciting
+Excruciating
+Explosive^Explosion
+Exquisite
+Extreme^X-Treme
+Fabulous
+Fancy
+Fantastic
+Fantasy
+Fatal
+Feverish
+Fiery
+Final
+Final Fantasy^Fantasy
+First-Person
+Fisher Price
+Flamboyant
+Fluffy
+Flying
+Forbidden
+Forgotten
+Frankenstein's
+French
+Frisky
+Fruity
+Full Metal
+Funky^Funk
+Furry
+Future
+Galactic
+Generic
+Geriatric
+German
+Ghetto
+Giant
+Glowing
+Go Go
+God of
+Golden
+Gothic^Goth
+Grand
+Great
+Grimy
+Guitar
+Happy
+Hardcore
+Haunted
+Hazardous
+Heavy
+Heavy Metal^Metal
+Heinous
+Helicopter
+Heroic^Hero|Heroes
+Hidden
+Hideous
+High-Speed^Speed
+Hillbilly
+Hindu
+Hip-Hop^Hippo
+History of the
+Hitler's^Nazi
+Ho-Hum
+Holy
+Horrifying
+Hyper
+Imperial
+Impossible
+In Search of
+In Search of the
+In the Lost Kingdom of
+In Your Face
+Inappropriate
+Inbred
+Incomprehensible
+Incredible
+Indian
+Indiana Jones and the
+Inept
+Infinite
+Ingenious
+Insane
+Intellectual
+Intelligent
+Intense
+Interactive
+International
+Internet
+Interstellar
+Invisible
+Irish
+Iron
+Irresistible
+Irritating
+Islamic
+Italian
+It's a Mad, Mad^Madness
+Jackie Chan's
+Jamaican
+Japanese
+Jedi
+Jewish
+Johnny Turbo's
+John Romero's
+Kabuki
+Kamikaze
+Kermit's
+Killer
+Kinect
+King of^King|Kingdom
+Kinky
+Kirby's
+Kosher
+Kung-fu
+Jack Thompson's
+Lair of the
+Latino
+Lazy
+Legacy of
+Legend of^Legend|Legends
+Legend of the^Legend|Legends
+Legendary^Legend|Legends
+Leisure Suit
+Lethal
+Little
+Looney Tunes
+Lord of the^Lord
+Lost
+Lovely^Love|of Love|Romance
+Low G
+Lucky
+Luigi's
+M.C. Escher's
+Madden
+Magic^of Magic|of Might and Magic
+Magical^Magic|of Magic|of Might and Magic
+Magnetic
+Major
+Manic^Mania|Maniac
+Maniac^Mania
+Mario's
+Mary Kate and Ashley's
+Master Chief's^Master
+Masters of^Master
+Masters of the^Master
+Maximum
+Mechanized
+Medieval
+Mega
+Mega Man's
+Merciless
+Metal
+Mexican
+Michael Jackson's
+Mickey's
+Micro
+Middle-Eastern^in the Middle East
+Mighty
+Mind-Bending
+Miniature^Dwarf|Gnome|Midget
+Miracle
+Monster^Monster Truck
+Monty Python's
+Morbid
+Morbidly Obese
+Mr.
+MTV's
+Muppet
+Musical^DJ|Music
+My First
+My Little
+My Very Own
+Mysterious^of Mystery
+Mystery^of Mystery
+Mystic^of Mystery
+Mystical^of Mystery
+Mythical
+Narcoleptic
+Nasty
+National Lampoon's
+Naughty
+NBA
+NCAA
+Nerf
+Neo
+Neon
+Neurotic
+New
+Night of the^Knights|Night|Nightmare
+Nighttime^Knights|Night|Nightmare
+Nihilistic
+Ninja
+No One Can Stop the
+Nostalgic
+Nuclear^Atomic
+Nudist
+Obsessive-Compulsive
+Occult
+Olympic
+Omega
+Orbital
+Pagan
+Panzer
+Papal
+Paranoid
+Pathetic
+Peaceful
+Perfect
+Perverted
+Phoenix Wright:
+Pixellated
+Planet of the^Planet
+Political
+Post-Apocalyptic
+Prehistoric
+Preschool
+Presidential
+Primal
+Pro
+Profane
+Professional^Pro
+Psychedelic
+Psycho
+Queen of the^Princess
+Quiet
+Rad
+Radical
+Radioactive
+Raging^Rage
+Real
+Red Hot
+Regal
+Relentless
+Religious
+Remote
+Renegade
+Retarded
+Retro
+Return of^Returns|Strikes Again|Strikes Back
+Return of the^Returns|Strikes Again|Strikes Back
+Revenge of^Revenge|- The Revenge
+Revenge of the^Revenge|- The Revenge
+Rise of the
+Robot
+Robotic^Robot
+Rock 'n' Roll
+Rocket-Powered
+Rockin'
+Rogue
+Romantic
+Royal
+Rural
+Rushing^Rush
+Russian
+Samba de
+Samurai
+Satan's
+Savage
+Save Yourself from the
+Scandinavian
+Scooby Doo and the
+Scottish
+Screaming
+Search for the
+Secret of the
+Sensual^Sex
+Sexy^Sex
+Shadow of the^Shadow
+Shady
+Shameful
+Shrunken^Midget
+Sid Meier's
+Silent
+Silly
+Sim^Simulator
+Sinister
+Sleazy
+Sleepy
+Small-Time
+Sonic's
+Soviet
+Space
+Special^Special Edition
+Spectacular
+Spectral^Ghost
+Spirit of the
+Spooky
+Spunky
+Star^All-Stars|Starring Mickey Mouse|Stars|Superstar
+Star Trek^All-Stars|Star|Starring Mickey Mouse|Stars|Superstar
+Star Wars^All-Stars|Star|Starring Mickey Mouse|Stars|Superstar
+Stealth
+Stoic
+Strategic
+Street
+Stupendous
+Stylish
+Subatomic
+Subterranean^Underground|Underworld
+Summer
+Super
+Super Sexy^Sex|Superstar
+Supreme
+Surprise
+Tactical^Tactics
+Tasteless
+Team
+Teenage
+Telekinetic
+Terrible
+The
+The Care Bears'
+The Castle of
+The Castle of the
+The Glory of
+The Great
+The Harlem Globetrotters:
+The Hunt For the
+The Incredible
+The Infernal
+The Last
+The Muppets^Muppets
+The Quest for the^Quest
+The Secret Weapon of the
+The Simpsons'
+The Sims:
+The Six Million Dollar
+Third-World
+Throbbing
+Tiger Woods'
+Tiny^Midget
+Tom Clancy's
+Tony Hawk's
+Topsy-Turvy
+Toxic
+Transvestite
+Trendy
+Tribal
+Tropical
+True Crime:
+Turbo
+Twin
+Twisted
+Ultimate
+Ultra
+Ultraviolent^Ultra
+Unbelievable
+Undead
+Undercover^Under Fire|Underwear|Underground|Underworld
+Underground^Under Fire|Underwear|Underground|Underworld
+Underwater^Under Fire|Underwear|Underground|Underworld
+Unforgettable
+Unholy
+Unpleasant
+Unreal
+Unremarkable
+Unstoppable
+Urban
+Vampire
+Vegetarian
+Viking
+Violent
+Virtua
+Virtual
+Wacky
+Wandering
+War of the^Warfare|Warrior|Wars|- Total War
+We Love
+Weary
+Wild^Gone Wild
+Wonderous
+Wooden
+World^World Cup|World Tour
+World of^World|World Cup|World Tour
+Wrath of the
+WWII^Warfare|Warrior|Wars|World|World Cup|World Tour|- Total War
+Ye Olde
+Yoshi's
+Zany
+Zombie^Zombies
+----
+3D
+Acid
+Aerobics
+Afro
+Alien
+Alligator
+Amish
+Android
+Animal
+Arcade
+Architecture
+Army
+Assault
+Axe
+Badminton^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Baking
+Ballet
+Balloon
+Banana
+Bandicoot
+Banjo
+Barbarian
+Barcode
+Baseball^Base|Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Basketball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Bass
+Batman
+Battle^Battalion
+Battleship^Battle|Battalion
+Bazooka
+Beach
+Beast
+Beat
+Beautician
+Bedtime
+Bible
+Big Game Hunter^Hunt|Hunter
+Bimbo
+Bingo
+Biplane
+Blade
+Blimp
+Blood^Bloodbath|of the Blood God
+BMX
+Bobsled
+Bomb
+Bomberman
+Bong
+Bongo
+Booty
+Bow Hunter^Hunt|Hunter
+Bowling^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Boxing^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Breakdancing^Dance Mix|Dance Party|Dancers
+Bubble
+Bubblegum
+Buddhist
+Bungie
+Burger
+Business
+Cannibal
+Car
+Cardboard
+Carnival
+Casino
+Castlevania
+Catapult
+Caveman^Man
+Chainsaw
+Chase
+Cheese
+Chef
+Chess
+Chicken
+Chipmunk
+Chocobo
+Circus
+City
+College
+Combat
+Computer
+Conga
+Cookie
+Cooking
+Cowboy
+Cricket^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Croquet^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Crowbar
+Crystal
+Cyborg
+Dance^Dance Mix|Dance Party|Dancers
+Dating
+Death^Deathmatch|of Death|of the Dead
+Deer Hunter
+Demon
+Dentist
+Desert^in the Desert
+Devil
+Dinosaur
+Disco
+Dodgeball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Dog
+Donkey
+Dragon
+Driving
+Drug-Dealing
+Duck
+Dungeon
+Dwarf
+Elevator
+Equestrian
+Fashion
+Fantasy
+Farm^Farmer
+Fencing
+Fighter^Fight|Fight Club
+Fire
+Fishing^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Flatulence
+Florist
+Football^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Forklift
+Frisbee
+Frog
+Fun
+Fun Noodle
+Funk
+Furry
+Ghost
+Gimp
+Gnome
+Go-Kart
+Goblin
+Godzilla
+Golf
+Gopher
+Goth
+Graveyard
+Grizzly Bear
+Guitar
+Gun
+Hair Salon
+Hammer
+Hamster
+Handgun
+Hang Glider
+Hardware
+Harpoon
+Harvest
+Helicopter
+Hillbilly
+Hippo
+Hitman^Man
+Hobo
+Hockey
+Hoedown^Beatdown|Showdown|Smackdown|Takedown
+Hovercraft
+Horse Racing^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Ice
+Ice Cream
+Indian
+Insect
+Internet
+Jackhammer
+Janitor
+Jazz
+Jetpack
+Jetski
+Juggalo
+Jungle
+Kabuki
+Kangaroo
+Karaoke
+Karate
+Kart
+Katana
+Kitchen
+Kung-fu
+Lacrosse^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Landmine
+Laser
+Lawnmower
+Lego
+Leisure Suit
+Lightning
+Limbo
+Lizard
+Llama
+Love^of Love|Romance
+Lowrider
+Mafia
+Magic^of Magic|of Might and Magic
+Mahjong
+Makeout
+Makeover
+Mall
+Manlove
+Matador
+Math
+Maze
+Mech
+Metal
+Midget
+Military
+Mind Control
+Monkey
+Monster
+Monster Truck
+Moon
+Moped
+Motorcycle
+Motocross
+Mountain Climber
+Mummy
+Mushroom
+Music^DJ
+Mutant
+NASCAR
+Nazi
+Night^Knights|Nightmare
+Ninja
+Nuclear
+Nudist
+Octopus
+Office
+Ostrich
+Outlaw
+Pachinko
+Paintball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Penguin
+Piano
+Pinball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Ping Pong^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Pirate
+Platypus
+Plumber
+Plunger
+Pogo
+Pokemon
+Police
+Polka
+Pony
+Porn
+Princess
+Prison
+Programming
+Punching
+Puppy
+Puzzle
+Quantum
+Quiz
+Rabbit
+Raccoon
+Racing^Racer
+Railroad
+Rainbow
+River
+Robot
+Rocket
+Rodeo
+Rollerball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Rugby^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Sailboat
+Sailor
+Samurai
+Sandwich
+Scooter
+Scorched Earth
+Sewer
+Sex
+Shadow
+Shark
+Shaving
+Shock
+Shopping
+Shotgun
+Skate
+Skydiving
+Sloth
+Sniper
+Snowboard
+Soccer
+Software
+Spatula
+Speed
+Spelling
+Spelunking
+Spider
+Spork
+Square Dancing^Dance Mix|Dance Party|Dancers
+Squirrel
+Stapler
+STD
+Stick
+Stunt
+Submarine
+Sumo
+Sudoku
+Sunshine
+Surf
+Surgery
+Sushi
+Sword
+Tank
+Techno
+Tennis
+Terrorist
+Tetris
+Theme Park^Park
+Thief
+Thunder
+Toon
+Trailer Park^Park
+Train
+Trampoline
+Transvestite
+Tricycle
+Turtle
+Typing
+UFO
+Underwear^Under Fire|Underground|Underworld
+Unicorn
+Unicycle
+Valkyrie
+Vampire
+Vegetarian
+Vigilante
+Viking
+Vocabulary
+Volleyball^Baseball|Basketball|Boxing|Football|Paintbrawl|Pinball|Polo
+Wagon
+Walrus
+Wedding
+Weight Loss
+Werewolf
+Whale
+Wheelchair
+Wizard
+Workout
+Worm
+Wrestling
+Writing
+WWE
+WWII^Warfare|Warrior|Wars|World|World Cup|World Tour|- Total War
+Yak
+Yeti
+Yoga
+Zamboni
+Zombie^Zombies
+----
+- 2nd Impact
+- 3rd Strike
+1942
+25th Anniversary Edition
+2K
+2000
+3000
+3D
+64
+95
+Academy
+Advance
+Adventure
+Agent
+All-Stars
+Alpha
+Anarchy
+Annihilation
+Anthology
+Apocalypse
+Arena
+Armada
+Armageddon
+Assassins
+Assault
+at the Olympics
+Attack
+Babies
+Bandit
+Bandits
+Bastards
+Battle
+Battalion
+Base
+Baseball
+Basketball
+Beatdown
+Beta
+Blast
+Blaster
+Bloodbath
+Boxing
+Boy
+Brawl
+Brothers
+Camp
+Caper
+Carnage
+Castle
+CD
+Challenge
+Championship
+Chase
+Choreographer
+Chronicles
+City
+Co-Op
+Collection
+- Collector's Edition
+College
+Colosseum
+Combat
+Commander
+Commando
+Competition
+Conflict
+Connection
+Conquest
+Conspiracy
+Conundrum
+Corps
+Country
+Creator
+Crime Scene Investigation
+Crisis
+Crusade
+Crusader
+Dance Mix
+Dance Party
+Dancers
+Daredevils
+Dash
+Deathmatch
+Deluxe
+Demolition
+Derby
+Desperadoes
+Destruction
+Detective
+Diesel
+Disaster
+DJ
+Domination
+Dreamland
+DS
+Dudes
+Dungeon
+DX
+Dynasty
+Dystopia
+Empire
+Encounter
+Enforcer
+Epidemic
+Espionage
+EX
+Exhibition
+Experience
+Expert
+Explorer
+Explosion
+Express
+Extra
+Extravaganza
+Factory
+Family
+Fandango
+Fantasy
+Farmer
+Fest
+Feud
+Fever
+Fiasco
+Fiesta
+Fight
+Fight Club
+Fighter
+Football
+For Kids
+Force
+Forever
+Fortress
+Freak
+Frenzy
+from Hell
+from Mars
+from Outer Space
+from Planet X
+Fun
+Gaiden
+Gang
+Girl
+Gold
+Gone Wild
+Gladiator
+Groove
+GT
+Havoc
+Hell
+Hero
+Heroes
+Hoedown
+Hop-A-Bout
+Horde
+Horror
+Hospital
+- Hot Pursuit
+House
+Hunt
+Hunter
+II
+III
+Ignition
+in Africa
+in Busytown
+in Crazyland
+in Middle-Earth
+in My Pocket
+in Space
+in the Bayou
+in the Dark
+in the Desert
+in the Hood
+in the Magic Kingdom
+in the Middle East
+in the Outback
+in the Salad Kingdom
+in the Sky
+in Toyland
+in Vegas
+Incident
+Inferno
+Insanity
+Inspector
+Insurrection
+Interactive
+Interceptor
+Invaders
+Invasion
+Island
+Jam
+Jamboree
+Jihad
+Joe
+Journey
+Jr.
+Kid
+Kids
+King
+Kingdom
+Knights
+Kombat
+Legend
+Legends
+- Limited Edition
+Live
+Lord
+Machine
+Madness
+Man
+Mania
+Maniac
+Mansion
+Marines
+Massacre
+Master
+Maxx
+Mayhem
+Melee
+Mission
+Munchers
+Nation
+Nightmare
+Nitro
+Odyssey
+of Death
+of Doom
+of Fury
+of Love
+of Magic
+of Might and Magic
+of Mystery
+of the Blood God
+of the Damned
+of the Dead
+of the Deep
+of the Third Reich
+on the Oregon Trail
+Offensive
+Omega
+on the High Seas
+On The Road
+on Wheels
+Online
+Onslaught
+Operation
+Operatives
+Oppression
+Orchestra
+Over Normandy
+Overdrive
+Overload
+Overlords
+Paintbrawl
+Palace
+Panic
+Paratroopers
+Park
+Party
+Patrol
+Phonics
+Pimps
+Pinball
+Pioneer
+Planet
+Playhouse
+Plus
+Police
+Polo
+Posse
+Power
+Preacher
+Princess
+Pro
+Project
+Prophecy
+Psychiatrist
+Punch-Out!!
+Punishment
+Quest
+Quiz
+Racer
+Rage
+Raider
+Rally
+Rampage
+Rangers
+Ransom
+Rave
+Rebellion
+Reloaded
+Remix
+Rescue
+Restaurant
+Returns
+Revenge
+Revisited
+Revolution
+Rider
+Riders
+Rocket
+Romance
+Romp
+Roundup
+Runner
+Rush
+Safari
+Saga
+Saloon
+Scam
+Scandal
+School
+Shack
+Shoot
+Shootout
+Showdown
+Siege
+Simulator
+Sisters
+Slam
+Slaughter
+Slayer
+Smackdown
+Smash
+Smuggler
+Solid
+Soldier
+Special Edition
+Spectacular
+Spies
+Spree
+Squadron
+Stadium
+Starring Mickey Mouse
+Stars
+Story
+Strike Force
+Strikes Again
+Strikes Back
+Struggle
+Studio
+Summit
+Summoner
+Superstar
+Symphony
+Syndicate
+Syndrome
+Tactics
+Takedown
+Tale
+Task Force
+Temple
+Terror
+Thieves
+- The Card Game
+- The Dark Project
+- The Gathering Storm
+- The Lost Levels
+- The Movie
+- The Next Generation
+- The Quickening
+- The Resistance
+- The Revenge
+Through Time
+Throwdown
+- Total War
+Tournament
+Trader
+Train
+Trainer
+Training
+Tribe
+Trilogy
+Trivia
+Troopers
+Turbo
+Tycoon
+Ultra
+Unit
+Uncensored
+Underground
+Underworld
+Universe
+Unleashed
+Uprising
+Vengeance
+Voyage
+vs. Capcom
+vs. Street Fighter
+vs. The Space Mutants
+Warfare
+Warrior
+Wars
+Wasteland
+with Friends
+World
+World Cup
+World Tour
+Wranglers
+X
+XP
+XXX
+X-treme
+Yoga
+Z
+Zombies
+Zone
diff --git a/assets/php/vendor/nubs/random-name-generator/tests/AllTest.php b/assets/php/vendor/nubs/random-name-generator/tests/AllTest.php
new file mode 100644
index 0000000..8049ab7
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/tests/AllTest.php
@@ -0,0 +1,72 @@
+
+ */
+class AllTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Verify basic behavior of getName().
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::create
+ * @covers ::getName
+ * @uses \Nubs\RandomNameGenerator\Alliteration
+ * @uses \Nubs\RandomNameGenerator\Vgng
+ *
+ * @return void
+ */
+ public function getNameBasic()
+ {
+ $generator = All::create();
+ $name = $generator->getName();
+ $this->assertRegexp('/.+/', $name);
+ }
+
+ /**
+ * Verify basic behavior of getName() with a forced random generator.
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::create
+ * @covers ::getName
+ * @uses \Nubs\RandomNameGenerator\Alliteration
+ *
+ * @return void
+ */
+ public function getNameForced()
+ {
+ $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator');
+ $numberGenerator->expects($this->exactly(2))->method('getInt')->will($this->onConsecutiveCalls(20, 5));
+ $randomizer = new Randomizer($numberGenerator);
+
+ $generator = new All([new Alliteration($randomizer)]);
+ $this->assertSame('Black Bear', $generator->getName());
+ }
+
+ /**
+ * Verify basic behavior of __toString().
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::create
+ * @covers ::__toString
+ * @covers ::getName
+ * @uses \Nubs\RandomNameGenerator\Alliteration
+ * @uses \Nubs\RandomNameGenerator\Vgng
+ *
+ * @return void
+ */
+ public function toStringBasic()
+ {
+ $generator = All::create();
+ $name = (string)$generator;
+ $this->assertRegexp('/.+/', $name);
+ }
+}
diff --git a/assets/php/vendor/nubs/random-name-generator/tests/AlliterationTest.php b/assets/php/vendor/nubs/random-name-generator/tests/AlliterationTest.php
new file mode 100644
index 0000000..0b47343
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/tests/AlliterationTest.php
@@ -0,0 +1,66 @@
+
+ */
+class AlliterationTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Verify basic behavior of getName().
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::getName
+ *
+ * @return void
+ */
+ public function getNameBasic()
+ {
+ $generator = new Alliteration();
+ $parts = explode(' ', $generator->getName());
+ $this->assertSame(2, count($parts));
+ $this->assertSame($parts[0][0], $parts[1][0]);
+ }
+
+ /**
+ * Verify basic behavior of getName() with a forced random generator.
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::getName
+ *
+ * @return void
+ */
+ public function getNameForced()
+ {
+ $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator');
+ $numberGenerator->expects($this->exactly(2))->method('getInt')->will($this->onConsecutiveCalls(20, 5));
+ $randomizer = new Randomizer($numberGenerator);
+
+ $generator = new Alliteration($randomizer);
+ $this->assertSame('Black Bear', $generator->getName());
+ }
+
+ /**
+ * Verify basic behavior of __toString().
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::__toString
+ * @covers ::getName
+ *
+ * @return void
+ */
+ public function toStringBasic()
+ {
+ $generator = new Alliteration();
+ $parts = explode(' ', (string)$generator);
+ $this->assertSame(2, count($parts));
+ $this->assertSame($parts[0][0], $parts[1][0]);
+ }
+}
diff --git a/assets/php/vendor/nubs/random-name-generator/tests/VgngTest.php b/assets/php/vendor/nubs/random-name-generator/tests/VgngTest.php
new file mode 100644
index 0000000..a301b81
--- /dev/null
+++ b/assets/php/vendor/nubs/random-name-generator/tests/VgngTest.php
@@ -0,0 +1,67 @@
+
+ */
+class VgngTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Verify that getName returns the expected name.
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::getName
+ */
+ public function getNameBasic()
+ {
+ $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator');
+ $numberGenerator->expects($this->exactly(3))->method('getInt')->will($this->returnValue(1));
+ $randomizer = new Randomizer($numberGenerator);
+
+ $vgng = new Vgng($randomizer);
+
+ $this->assertSame('8-Bit Acid - 3rd Strike', $vgng->getName());
+ }
+
+ /**
+ * Verify that getName returns a name without similar strings.
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::getName
+ */
+ public function getNameSimilarName()
+ {
+ $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator');
+ $numberGenerator->expects($this->exactly(4))->method('getInt')->will($this->onConsecutiveCalls(0, 0, 2, 10));
+ $randomizer = new Randomizer($numberGenerator);
+
+ $vgng = new Vgng($randomizer);
+
+ $this->assertSame('3D Aerobics Academy', $vgng->getName());
+ }
+
+ /**
+ * Verify that toString returns the expected name.
+ *
+ * @test
+ * @covers ::__construct
+ * @covers ::__toString
+ * @covers ::getName
+ */
+ public function toStringBasic()
+ {
+ $numberGenerator = $this->createMock('\Cinam\Randomizer\NumberGenerator');
+ $numberGenerator->expects($this->exactly(3))->method('getInt')->will($this->returnValue(1));
+ $randomizer = new Randomizer($numberGenerator);
+
+ $vgng = new Vgng($randomizer);
+
+ $this->assertSame('8-Bit Acid - 3rd Strike', (string)$vgng);
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/LICENSE b/assets/php/vendor/paragonie/random_compat/LICENSE
new file mode 100644
index 0000000..45c7017
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Paragon Initiative Enterprises
+
+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.
+
diff --git a/assets/php/vendor/paragonie/random_compat/build-phar.sh b/assets/php/vendor/paragonie/random_compat/build-phar.sh
new file mode 100755
index 0000000..b4a5ba3
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/build-phar.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )
+
+php -dphar.readonly=0 "$basedir/other/build_phar.php" $*
\ No newline at end of file
diff --git a/assets/php/vendor/paragonie/random_compat/composer.json b/assets/php/vendor/paragonie/random_compat/composer.json
new file mode 100644
index 0000000..1c5978c
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/composer.json
@@ -0,0 +1,37 @@
+{
+ "name": "paragonie/random_compat",
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "random",
+ "pseudorandom"
+ ],
+ "license": "MIT",
+ "type": "library",
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/paragonie/random_compat/issues",
+ "email": "info@paragonie.com",
+ "source": "https://github.com/paragonie/random_compat"
+ },
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "autoload": {
+ "files": [
+ "lib/random.php"
+ ]
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey
new file mode 100644
index 0000000..eb50ebf
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
+pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
+-----END PUBLIC KEY-----
diff --git a/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc
new file mode 100644
index 0000000..6a1d7f3
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v2.0.22 (MingW32)
+
+iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
+QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
+1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
+NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
+NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
+JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
+=B6+8
+-----END PGP SIGNATURE-----
diff --git a/assets/php/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/assets/php/vendor/paragonie/random_compat/lib/byte_safe_strings.php
new file mode 100644
index 0000000..3de86b2
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/lib/byte_safe_strings.php
@@ -0,0 +1,181 @@
+ RandomCompat_strlen($binary_string)) {
+ return '';
+ }
+
+ return (string) mb_substr($binary_string, $start, $length, '8bit');
+ }
+
+ } else {
+
+ /**
+ * substr() implementation that isn't brittle to mbstring.func_overload
+ *
+ * This version just uses the default substr()
+ *
+ * @param string $binary_string
+ * @param int $start
+ * @param int $length (optional)
+ *
+ * @throws TypeError
+ *
+ * @return string
+ */
+ function RandomCompat_substr($binary_string, $start, $length = null)
+ {
+ if (!is_string($binary_string)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): First argument should be a string'
+ );
+ }
+
+ if (!is_int($start)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): Second argument should be an integer'
+ );
+ }
+
+ if ($length !== null) {
+ if (!is_int($length)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): Third argument should be an integer, or omitted'
+ );
+ }
+
+ return (string) substr($binary_string, $start, $length);
+ }
+
+ return (string) substr($binary_string, $start);
+ }
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/lib/cast_to_int.php b/assets/php/vendor/paragonie/random_compat/lib/cast_to_int.php
new file mode 100644
index 0000000..9a4fab9
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/lib/cast_to_int.php
@@ -0,0 +1,75 @@
+ operators might accidentally let a float
+ * through.
+ *
+ * @param int|float $number The number we want to convert to an int
+ * @param bool $fail_open Set to true to not throw an exception
+ *
+ * @return float|int
+ * @psalm-suppress InvalidReturnType
+ *
+ * @throws TypeError
+ */
+ function RandomCompat_intval($number, $fail_open = false)
+ {
+ if (is_int($number) || is_float($number)) {
+ $number += 0;
+ } elseif (is_numeric($number)) {
+ $number += 0;
+ }
+
+ if (
+ is_float($number)
+ &&
+ $number > ~PHP_INT_MAX
+ &&
+ $number < PHP_INT_MAX
+ ) {
+ $number = (int) $number;
+ }
+
+ if (is_int($number)) {
+ return (int) $number;
+ } elseif (!$fail_open) {
+ throw new TypeError(
+ 'Expected an integer.'
+ );
+ }
+ return $number;
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/lib/error_polyfill.php b/assets/php/vendor/paragonie/random_compat/lib/error_polyfill.php
new file mode 100644
index 0000000..6a91990
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/lib/error_polyfill.php
@@ -0,0 +1,49 @@
+= 70000) {
+ return;
+}
+
+if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
+ define('RANDOM_COMPAT_READ_BUFFER', 8);
+}
+
+$RandomCompatDIR = dirname(__FILE__);
+
+require_once $RandomCompatDIR . '/byte_safe_strings.php';
+require_once $RandomCompatDIR . '/cast_to_int.php';
+require_once $RandomCompatDIR . '/error_polyfill.php';
+
+if (!is_callable('random_bytes')) {
+ /**
+ * PHP 5.2.0 - 5.6.x way to implement random_bytes()
+ *
+ * We use conditional statements here to define the function in accordance
+ * to the operating environment. It's a micro-optimization.
+ *
+ * In order of preference:
+ * 1. Use libsodium if available.
+ * 2. fread() /dev/urandom if available (never on Windows)
+ * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
+ * 4. COM('CAPICOM.Utilities.1')->GetRandom()
+ *
+ * See RATIONALE.md for our reasoning behind this particular order
+ */
+ if (extension_loaded('libsodium')) {
+ // See random_bytes_libsodium.php
+ if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
+ require_once $RandomCompatDIR . '/random_bytes_libsodium.php';
+ } elseif (method_exists('Sodium', 'randombytes_buf')) {
+ require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php';
+ }
+ }
+
+ /**
+ * Reading directly from /dev/urandom:
+ */
+ if (DIRECTORY_SEPARATOR === '/') {
+ // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
+ // way to exclude Windows.
+ $RandomCompatUrandom = true;
+ $RandomCompat_basedir = ini_get('open_basedir');
+
+ if (!empty($RandomCompat_basedir)) {
+ $RandomCompat_open_basedir = explode(
+ PATH_SEPARATOR,
+ strtolower($RandomCompat_basedir)
+ );
+ $RandomCompatUrandom = (array() !== array_intersect(
+ array('/dev', '/dev/', '/dev/urandom'),
+ $RandomCompat_open_basedir
+ ));
+ $RandomCompat_open_basedir = null;
+ }
+
+ if (
+ !is_callable('random_bytes')
+ &&
+ $RandomCompatUrandom
+ &&
+ @is_readable('/dev/urandom')
+ ) {
+ // Error suppression on is_readable() in case of an open_basedir
+ // or safe_mode failure. All we care about is whether or not we
+ // can read it at this point. If the PHP environment is going to
+ // panic over trying to see if the file can be read in the first
+ // place, that is not helpful to us here.
+
+ // See random_bytes_dev_urandom.php
+ require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php';
+ }
+ // Unset variables after use
+ $RandomCompat_basedir = null;
+ } else {
+ $RandomCompatUrandom = false;
+ }
+
+ /**
+ * mcrypt_create_iv()
+ *
+ * We only want to use mcypt_create_iv() if:
+ *
+ * - random_bytes() hasn't already been defined
+ * - the mcrypt extensions is loaded
+ * - One of these two conditions is true:
+ * - We're on Windows (DIRECTORY_SEPARATOR !== '/')
+ * - We're not on Windows and /dev/urandom is readabale
+ * (i.e. we're not in a chroot jail)
+ * - Special case:
+ * - If we're not on Windows, but the PHP version is between
+ * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will
+ * hang indefinitely. This is bad.
+ * - If we're on Windows, we want to use PHP >= 5.3.7 or else
+ * we get insufficient entropy errors.
+ */
+ if (
+ !is_callable('random_bytes')
+ &&
+ // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
+ (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
+ &&
+ // Prevent this code from hanging indefinitely on non-Windows;
+ // see https://bugs.php.net/bug.php?id=69833
+ (
+ DIRECTORY_SEPARATOR !== '/' ||
+ (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
+ )
+ &&
+ extension_loaded('mcrypt')
+ ) {
+ // See random_bytes_mcrypt.php
+ require_once $RandomCompatDIR . '/random_bytes_mcrypt.php';
+ }
+ $RandomCompatUrandom = null;
+
+ /**
+ * This is a Windows-specific fallback, for when the mcrypt extension
+ * isn't loaded.
+ */
+ if (
+ !is_callable('random_bytes')
+ &&
+ extension_loaded('com_dotnet')
+ &&
+ class_exists('COM')
+ ) {
+ $RandomCompat_disabled_classes = preg_split(
+ '#\s*,\s*#',
+ strtolower(ini_get('disable_classes'))
+ );
+
+ if (!in_array('com', $RandomCompat_disabled_classes)) {
+ try {
+ $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
+ if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
+ // See random_bytes_com_dotnet.php
+ require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php';
+ }
+ } catch (com_exception $e) {
+ // Don't try to use it.
+ }
+ }
+ $RandomCompat_disabled_classes = null;
+ $RandomCompatCOMtest = null;
+ }
+
+ /**
+ * throw new Exception
+ */
+ if (!is_callable('random_bytes')) {
+ /**
+ * We don't have any more options, so let's throw an exception right now
+ * and hope the developer won't let it fail silently.
+ *
+ * @param mixed $length
+ * @psalm-suppress MissingReturnType
+ * @throws Exception
+ * @return string
+ */
+ function random_bytes($length)
+ {
+ unset($length); // Suppress "variable not used" warnings.
+ throw new Exception(
+ 'There is no suitable CSPRNG installed on your system'
+ );
+ return '';
+ }
+ }
+}
+
+if (!is_callable('random_int')) {
+ require_once $RandomCompatDIR . '/random_int.php';
+}
+
+$RandomCompatDIR = null;
diff --git a/assets/php/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php
new file mode 100644
index 0000000..fc1926e
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php
@@ -0,0 +1,88 @@
+GetRandom($bytes, 0));
+ if (RandomCompat_strlen($buf) >= $bytes) {
+ /**
+ * Return our random entropy buffer here:
+ */
+ return RandomCompat_substr($buf, 0, $bytes);
+ }
+ ++$execCount;
+ } while ($execCount < $bytes);
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Could not gather sufficient random data'
+ );
+ }
+}
\ No newline at end of file
diff --git a/assets/php/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php
new file mode 100644
index 0000000..df5b915
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php
@@ -0,0 +1,167 @@
+ 0);
+
+ /**
+ * Is our result valid?
+ */
+ if (is_string($buf)) {
+ if (RandomCompat_strlen($buf) === $bytes) {
+ /**
+ * Return our random entropy buffer here:
+ */
+ return $buf;
+ }
+ }
+ }
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Error reading from source device'
+ );
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php
new file mode 100644
index 0000000..4af1a24
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php
@@ -0,0 +1,88 @@
+ 2147483647) {
+ $buf = '';
+ for ($i = 0; $i < $bytes; $i += 1073741824) {
+ $n = ($bytes - $i) > 1073741824
+ ? 1073741824
+ : $bytes - $i;
+ $buf .= \Sodium\randombytes_buf($n);
+ }
+ } else {
+ $buf = \Sodium\randombytes_buf($bytes);
+ }
+
+ if ($buf !== false) {
+ if (RandomCompat_strlen($buf) === $bytes) {
+ return $buf;
+ }
+ }
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Could not gather sufficient random data'
+ );
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php
new file mode 100644
index 0000000..705af52
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php
@@ -0,0 +1,92 @@
+ 2147483647) {
+ for ($i = 0; $i < $bytes; $i += 1073741824) {
+ $n = ($bytes - $i) > 1073741824
+ ? 1073741824
+ : $bytes - $i;
+ $buf .= Sodium::randombytes_buf((int) $n);
+ }
+ } else {
+ $buf .= Sodium::randombytes_buf((int) $bytes);
+ }
+
+ if (is_string($buf)) {
+ if (RandomCompat_strlen($buf) === $bytes) {
+ return $buf;
+ }
+ }
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Could not gather sufficient random data'
+ );
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php
new file mode 100644
index 0000000..aac9c01
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php
@@ -0,0 +1,77 @@
+ operators might accidentally let a float
+ * through.
+ */
+
+ try {
+ $min = RandomCompat_intval($min);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_int(): $min must be an integer'
+ );
+ }
+
+ try {
+ $max = RandomCompat_intval($max);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_int(): $max must be an integer'
+ );
+ }
+
+ /**
+ * Now that we've verified our weak typing system has given us an integer,
+ * let's validate the logic then we can move forward with generating random
+ * integers along a given range.
+ */
+ if ($min > $max) {
+ throw new Error(
+ 'Minimum value must be less than or equal to the maximum value'
+ );
+ }
+
+ if ($max === $min) {
+ return (int) $min;
+ }
+
+ /**
+ * Initialize variables to 0
+ *
+ * We want to store:
+ * $bytes => the number of random bytes we need
+ * $mask => an integer bitmask (for use with the &) operator
+ * so we can minimize the number of discards
+ */
+ $attempts = $bits = $bytes = $mask = $valueShift = 0;
+
+ /**
+ * At this point, $range is a positive number greater than 0. It might
+ * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
+ * a float and we will lose some precision.
+ */
+ $range = $max - $min;
+
+ /**
+ * Test for integer overflow:
+ */
+ if (!is_int($range)) {
+
+ /**
+ * Still safely calculate wider ranges.
+ * Provided by @CodesInChaos, @oittaa
+ *
+ * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
+ *
+ * We use ~0 as a mask in this case because it generates all 1s
+ *
+ * @ref https://eval.in/400356 (32-bit)
+ * @ref http://3v4l.org/XX9r5 (64-bit)
+ */
+ $bytes = PHP_INT_SIZE;
+ $mask = ~0;
+
+ } else {
+
+ /**
+ * $bits is effectively ceil(log($range, 2)) without dealing with
+ * type juggling
+ */
+ while ($range > 0) {
+ if ($bits % 8 === 0) {
+ ++$bytes;
+ }
+ ++$bits;
+ $range >>= 1;
+ $mask = $mask << 1 | 1;
+ }
+ $valueShift = $min;
+ }
+
+ $val = 0;
+ /**
+ * Now that we have our parameters set up, let's begin generating
+ * random integers until one falls between $min and $max
+ */
+ do {
+ /**
+ * The rejection probability is at most 0.5, so this corresponds
+ * to a failure probability of 2^-128 for a working RNG
+ */
+ if ($attempts > 128) {
+ throw new Exception(
+ 'random_int: RNG is broken - too many rejections'
+ );
+ }
+
+ /**
+ * Let's grab the necessary number of random bytes
+ */
+ $randomByteString = random_bytes($bytes);
+
+ /**
+ * Let's turn $randomByteString into an integer
+ *
+ * This uses bitwise operators (<< and |) to build an integer
+ * out of the values extracted from ord()
+ *
+ * Example: [9F] | [6D] | [32] | [0C] =>
+ * 159 + 27904 + 3276800 + 201326592 =>
+ * 204631455
+ */
+ $val &= 0;
+ for ($i = 0; $i < $bytes; ++$i) {
+ $val |= ord($randomByteString[$i]) << ($i * 8);
+ }
+
+ /**
+ * Apply mask
+ */
+ $val &= $mask;
+ $val += $valueShift;
+
+ ++$attempts;
+ /**
+ * If $val overflows to a floating point number,
+ * ... or is larger than $max,
+ * ... or smaller than $min,
+ * then try again.
+ */
+ } while (!is_int($val) || $val > $max || $val < $min);
+
+ return (int) $val;
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/other/build_phar.php b/assets/php/vendor/paragonie/random_compat/other/build_phar.php
new file mode 100644
index 0000000..70ef4b2
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/other/build_phar.php
@@ -0,0 +1,57 @@
+buildFromDirectory(dirname(__DIR__).'/lib');
+rename(
+ dirname(__DIR__).'/lib/index.php',
+ dirname(__DIR__).'/lib/random.php'
+);
+
+/**
+ * If we pass an (optional) path to a private key as a second argument, we will
+ * sign the Phar with OpenSSL.
+ *
+ * If you leave this out, it will produce an unsigned .phar!
+ */
+if ($argc > 1) {
+ if (!@is_readable($argv[1])) {
+ echo 'Could not read the private key file:', $argv[1], "\n";
+ exit(255);
+ }
+ $pkeyFile = file_get_contents($argv[1]);
+
+ $private = openssl_get_privatekey($pkeyFile);
+ if ($private !== false) {
+ $pkey = '';
+ openssl_pkey_export($private, $pkey);
+ $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
+
+ /**
+ * Save the corresponding public key to the file
+ */
+ if (!@is_readable($dist.'/random_compat.phar.pubkey')) {
+ $details = openssl_pkey_get_details($private);
+ file_put_contents(
+ $dist.'/random_compat.phar.pubkey',
+ $details['key']
+ );
+ }
+ } else {
+ echo 'An error occurred reading the private key from OpenSSL.', "\n";
+ exit(255);
+ }
+}
diff --git a/assets/php/vendor/paragonie/random_compat/psalm-autoload.php b/assets/php/vendor/paragonie/random_compat/psalm-autoload.php
new file mode 100644
index 0000000..d71d1b8
--- /dev/null
+++ b/assets/php/vendor/paragonie/random_compat/psalm-autoload.php
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/php/vendor/psr/http-message/CHANGELOG.md b/assets/php/vendor/psr/http-message/CHANGELOG.md
new file mode 100644
index 0000000..74b1ef9
--- /dev/null
+++ b/assets/php/vendor/psr/http-message/CHANGELOG.md
@@ -0,0 +1,36 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Updated all `@return self` annotation references in interfaces to use
+ `@return static`, which more closelly follows the semantics of the
+ specification.
+- Updated the `MessageInterface::getHeaders()` return annotation to use the
+ value `string[][]`, indicating the format is a nested array of strings.
+- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
+ to point to the correct section of RFC 7230.
+- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
+ to add the parameter name (`$uploadedFiles`).
+- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
+ method to correctly reference the method parameter (it was referencing an
+ incorrect parameter name previously).
+
+## 1.0.0 - 2016-05-18
+
+Initial stable release; reflects accepted PSR-7 specification.
diff --git a/assets/php/vendor/psr/http-message/LICENSE b/assets/php/vendor/psr/http-message/LICENSE
new file mode 100644
index 0000000..c2d8e45
--- /dev/null
+++ b/assets/php/vendor/psr/http-message/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 PHP Framework Interoperability Group
+
+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.
diff --git a/assets/php/vendor/psr/http-message/README.md b/assets/php/vendor/psr/http-message/README.md
new file mode 100644
index 0000000..2818533
--- /dev/null
+++ b/assets/php/vendor/psr/http-message/README.md
@@ -0,0 +1,13 @@
+PSR Http Message
+================
+
+This repository holds all interfaces/classes/traits related to
+[PSR-7](http://www.php-fig.org/psr/psr-7/).
+
+Note that this is not a HTTP message implementation of its own. It is merely an
+interface that describes a HTTP message. See the specification for more details.
+
+Usage
+-----
+
+We'll certainly need some stuff in here.
\ No newline at end of file
diff --git a/assets/php/vendor/psr/http-message/composer.json b/assets/php/vendor/psr/http-message/composer.json
new file mode 100644
index 0000000..b0d2937
--- /dev/null
+++ b/assets/php/vendor/psr/http-message/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/http-message",
+ "description": "Common interface for HTTP messages",
+ "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+ "homepage": "https://github.com/php-fig/http-message",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/assets/php/vendor/psr/http-message/src/MessageInterface.php b/assets/php/vendor/psr/http-message/src/MessageInterface.php
new file mode 100644
index 0000000..dd46e5e
--- /dev/null
+++ b/assets/php/vendor/psr/http-message/src/MessageInterface.php
@@ -0,0 +1,187 @@
+getHeaders() as $name => $values) {
+ * echo $name . ": " . implode(", ", $values);
+ * }
+ *
+ * // Emit headers iteratively:
+ * foreach ($message->getHeaders() as $name => $values) {
+ * foreach ($values as $value) {
+ * header(sprintf('%s: %s', $name, $value), false);
+ * }
+ * }
+ *
+ * While header names are not case-sensitive, getHeaders() will preserve the
+ * exact case in which headers were originally specified.
+ *
+ * @return string[][] Returns an associative array of the message's headers. Each
+ * key MUST be a header name, and each value MUST be an array of strings
+ * for that header.
+ */
+ public function getHeaders();
+
+ /**
+ * Checks if a header exists by the given case-insensitive name.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return bool Returns true if any header names match the given header
+ * name using a case-insensitive string comparison. Returns false if
+ * no matching header name is found in the message.
+ */
+ public function hasHeader($name);
+
+ /**
+ * Retrieves a message header value by the given case-insensitive name.
+ *
+ * This method returns an array of all the header values of the given
+ * case-insensitive header name.
+ *
+ * If the header does not appear in the message, this method MUST return an
+ * empty array.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string[] An array of string values as provided for the given
+ * header. If the header does not appear in the message, this method MUST
+ * return an empty array.
+ */
+ public function getHeader($name);
+
+ /**
+ * Retrieves a comma-separated string of the values for a single header.
+ *
+ * This method returns all of the header values of the given
+ * case-insensitive header name as a string concatenated together using
+ * a comma.
+ *
+ * NOTE: Not all header values may be appropriately represented using
+ * comma concatenation. For such headers, use getHeader() instead
+ * and supply your own delimiter when concatenating.
+ *
+ * If the header does not appear in the message, this method MUST return
+ * an empty string.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string A string of values as provided for the given header
+ * concatenated together using a comma. If the header does not appear in
+ * the message, this method MUST return an empty string.
+ */
+ public function getHeaderLine($name);
+
+ /**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * While header names are case-insensitive, the casing of the header will
+ * be preserved by this function, and returned from getHeaders().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withHeader($name, $value);
+
+ /**
+ * Return an instance with the specified header appended with the given value.
+ *
+ * Existing values for the specified header will be maintained. The new
+ * value(s) will be appended to the existing list. If the header did not
+ * exist previously, it will be added.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new header and/or value.
+ *
+ * @param string $name Case-insensitive header field name to add.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withAddedHeader($name, $value);
+
+ /**
+ * Return an instance without the specified header.
+ *
+ * Header resolution MUST be done without case-sensitivity.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the named header.
+ *
+ * @param string $name Case-insensitive header field name to remove.
+ * @return static
+ */
+ public function withoutHeader($name);
+
+ /**
+ * Gets the body of the message.
+ *
+ * @return StreamInterface Returns the body as a stream.
+ */
+ public function getBody();
+
+ /**
+ * Return an instance with the specified message body.
+ *
+ * The body MUST be a StreamInterface object.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return a new instance that has the
+ * new body stream.
+ *
+ * @param StreamInterface $body Body.
+ * @return static
+ * @throws \InvalidArgumentException When the body is not valid.
+ */
+ public function withBody(StreamInterface $body);
+}
diff --git a/assets/php/vendor/psr/http-message/src/RequestInterface.php b/assets/php/vendor/psr/http-message/src/RequestInterface.php
new file mode 100644
index 0000000..a96d4fd
--- /dev/null
+++ b/assets/php/vendor/psr/http-message/src/RequestInterface.php
@@ -0,0 +1,129 @@
+getQuery()`
+ * or from the `QUERY_STRING` server param.
+ *
+ * @return array
+ */
+ public function getQueryParams();
+
+ /**
+ * Return an instance with the specified query string arguments.
+ *
+ * These values SHOULD remain immutable over the course of the incoming
+ * request. They MAY be injected during instantiation, such as from PHP's
+ * $_GET superglobal, or MAY be derived from some other value such as the
+ * URI. In cases where the arguments are parsed from the URI, the data
+ * MUST be compatible with what PHP's parse_str() would return for
+ * purposes of how duplicate query parameters are handled, and how nested
+ * sets are handled.
+ *
+ * Setting query string arguments MUST NOT change the URI stored by the
+ * request, nor the values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated query string arguments.
+ *
+ * @param array $query Array of query string arguments, typically from
+ * $_GET.
+ * @return static
+ */
+ public function withQueryParams(array $query);
+
+ /**
+ * Retrieve normalized file upload data.
+ *
+ * This method returns upload metadata in a normalized tree, with each leaf
+ * an instance of Psr\Http\Message\UploadedFileInterface.
+ *
+ * These values MAY be prepared from $_FILES or the message body during
+ * instantiation, or MAY be injected via withUploadedFiles().
+ *
+ * @return array An array tree of UploadedFileInterface instances; an empty
+ * array MUST be returned if no data is present.
+ */
+ public function getUploadedFiles();
+
+ /**
+ * Create a new instance with the specified uploaded files.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+ * @return static
+ * @throws \InvalidArgumentException if an invalid structure is provided.
+ */
+ public function withUploadedFiles(array $uploadedFiles);
+
+ /**
+ * Retrieve any parameters provided in the request body.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, this method MUST
+ * return the contents of $_POST.
+ *
+ * Otherwise, this method may return any results of deserializing
+ * the request body content; as parsing returns structured content, the
+ * potential types MUST be arrays or objects only. A null value indicates
+ * the absence of body content.
+ *
+ * @return null|array|object The deserialized body parameters, if any.
+ * These will typically be an array or object.
+ */
+ public function getParsedBody();
+
+ /**
+ * Return an instance with the specified body parameters.
+ *
+ * These MAY be injected during instantiation.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, use this method
+ * ONLY to inject the contents of $_POST.
+ *
+ * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+ * deserializing the request body content. Deserialization/parsing returns
+ * structured data, and, as such, this method ONLY accepts arrays or objects,
+ * or a null value if nothing was available to parse.
+ *
+ * As an example, if content negotiation determines that the request data
+ * is a JSON payload, this method could be used to create a request
+ * instance with the deserialized parameters.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param null|array|object $data The deserialized body data. This will
+ * typically be in an array or object.
+ * @return static
+ * @throws \InvalidArgumentException if an unsupported argument type is
+ * provided.
+ */
+ public function withParsedBody($data);
+
+ /**
+ * Retrieve attributes derived from the request.
+ *
+ * The request "attributes" may be used to allow injection of any
+ * parameters derived from the request: e.g., the results of path
+ * match operations; the results of decrypting cookies; the results of
+ * deserializing non-form-encoded message bodies; etc. Attributes
+ * will be application and request specific, and CAN be mutable.
+ *
+ * @return array Attributes derived from the request.
+ */
+ public function getAttributes();
+
+ /**
+ * Retrieve a single derived request attribute.
+ *
+ * Retrieves a single derived request attribute as described in
+ * getAttributes(). If the attribute has not been previously set, returns
+ * the default value as provided.
+ *
+ * This method obviates the need for a hasAttribute() method, as it allows
+ * specifying a default value to return if the attribute is not found.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null);
+
+ /**
+ * Return an instance with the specified derived request attribute.
+ *
+ * This method allows setting a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $value The value of the attribute.
+ * @return static
+ */
+ public function withAttribute($name, $value);
+
+ /**
+ * Return an instance that removes the specified derived request attribute.
+ *
+ * This method allows removing a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @return static
+ */
+ public function withoutAttribute($name);
+}
diff --git a/assets/php/vendor/psr/http-message/src/StreamInterface.php b/assets/php/vendor/psr/http-message/src/StreamInterface.php
new file mode 100644
index 0000000..f68f391
--- /dev/null
+++ b/assets/php/vendor/psr/http-message/src/StreamInterface.php
@@ -0,0 +1,158 @@
+
+ * [user-info@]host[:port]
+ *
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ * @return string The URI authority, in "[user-info@]host[:port]" format.
+ */
+ public function getAuthority();
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no user information is present, this method MUST return an empty
+ * string.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ *
+ * @return string The URI user information, in "username[:password]" format.
+ */
+ public function getUserInfo();
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @return string The URI host.
+ */
+ public function getHost();
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ *
+ * @return null|int The URI port.
+ */
+ public function getPort();
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ * @return string The URI path.
+ */
+ public function getPath();
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no query string is present, this method MUST return an empty string.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ * @return string The URI query string.
+ */
+ public function getQuery();
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no fragment is present, this method MUST return an empty string.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ * @return string The URI fragment.
+ */
+ public function getFragment();
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * Implementations MUST support the schemes "http" and "https" case
+ * insensitively, and MAY accommodate other schemes if required.
+ *
+ * An empty scheme is equivalent to removing the scheme.
+ *
+ * @param string $scheme The scheme to use with the new instance.
+ * @return static A new instance with the specified scheme.
+ * @throws \InvalidArgumentException for invalid or unsupported schemes.
+ */
+ public function withScheme($scheme);
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; an empty string for the user is equivalent to removing user
+ * information.
+ *
+ * @param string $user The user name to use for authority.
+ * @param null|string $password The password associated with $user.
+ * @return static A new instance with the specified user information.
+ */
+ public function withUserInfo($user, $password = null);
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * An empty host value is equivalent to removing the host.
+ *
+ * @param string $host The hostname to use with the new instance.
+ * @return static A new instance with the specified host.
+ * @throws \InvalidArgumentException for invalid hostnames.
+ */
+ public function withHost($host);
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * Implementations MUST raise an exception for ports outside the
+ * established TCP and UDP port ranges.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param null|int $port The port to use with the new instance; a null value
+ * removes the port information.
+ * @return static A new instance with the specified port.
+ * @throws \InvalidArgumentException for invalid ports.
+ */
+ public function withPort($port);
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * If the path is intended to be domain-relative rather than path relative then
+ * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+ * are assumed to be relative to some base path known to the application or
+ * consumer.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @param string $path The path to use with the new instance.
+ * @return static A new instance with the specified path.
+ * @throws \InvalidArgumentException for invalid paths.
+ */
+ public function withPath($path);
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * An empty query string value is equivalent to removing the query string.
+ *
+ * @param string $query The query string to use with the new instance.
+ * @return static A new instance with the specified query string.
+ * @throws \InvalidArgumentException for invalid query strings.
+ */
+ public function withQuery($query);
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * An empty fragment value is equivalent to removing the fragment.
+ *
+ * @param string $fragment The fragment to use with the new instance.
+ * @return static A new instance with the specified fragment.
+ */
+ public function withFragment($fragment);
+
+ /**
+ * Return the string representation as a URI reference.
+ *
+ * Depending on which components of the URI are present, the resulting
+ * string is either a full URI or relative reference according to RFC 3986,
+ * Section 4.1. The method concatenates the various components of the URI,
+ * using the appropriate delimiters:
+ *
+ * - If a scheme is present, it MUST be suffixed by ":".
+ * - If an authority is present, it MUST be prefixed by "//".
+ * - The path can be concatenated without delimiters. But there are two
+ * cases where the path has to be adjusted to make the URI reference
+ * valid as PHP does not allow to throw an exception in __toString():
+ * - If the path is rootless and an authority is present, the path MUST
+ * be prefixed by "/".
+ * - If the path is starting with more than one "/" and no authority is
+ * present, the starting slashes MUST be reduced to one.
+ * - If a query is present, it MUST be prefixed by "?".
+ * - If a fragment is present, it MUST be prefixed by "#".
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @return string
+ */
+ public function __toString();
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/.gitignore b/assets/php/vendor/ratchet/rfc6455/.gitignore
new file mode 100644
index 0000000..42ab5d5
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/.gitignore
@@ -0,0 +1,4 @@
+composer.lock
+vendor
+tests/ab/reports
+reports
diff --git a/assets/php/vendor/ratchet/rfc6455/.travis.yml b/assets/php/vendor/ratchet/rfc6455/.travis.yml
new file mode 100644
index 0000000..11d51b4
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/.travis.yml
@@ -0,0 +1,20 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm
+
+before_install:
+ - export PATH=$HOME/.local/bin:$PATH
+ - pip install --user autobahntestsuite
+ - pip list --user autobahntestsuite
+
+before_script:
+ - composer install
+ - sh tests/ab/run_ab_tests.sh
+
+script:
+ - vendor/bin/phpunit
diff --git a/assets/php/vendor/ratchet/rfc6455/LICENSE b/assets/php/vendor/ratchet/rfc6455/LICENSE
new file mode 100644
index 0000000..7f8c128
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2016 Chris Boden
+
+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.
diff --git a/assets/php/vendor/ratchet/rfc6455/README.md b/assets/php/vendor/ratchet/rfc6455/README.md
new file mode 100644
index 0000000..7c09148
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/README.md
@@ -0,0 +1,13 @@
+# RFC6455 - The WebSocket Protocol
+
+[](https://travis-ci.org/ratchetphp/RFC6455)
+
+
+This library a protocol handler for the RFC6455 specification.
+It contains components for both server and client side handshake and messaging protocol negotation.
+
+Aspects that are left open to interpertation in the specification are also left open in this library.
+It is up to the implementation to determine how those interpertations are to be dealt with.
+
+This library is independent, framework agnostic, and does not deal with any I/O.
+HTTP upgrade negotiation integration points are handled with PSR-7 interfaces.
diff --git a/assets/php/vendor/ratchet/rfc6455/composer.json b/assets/php/vendor/ratchet/rfc6455/composer.json
new file mode 100644
index 0000000..224066b
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/composer.json
@@ -0,0 +1,32 @@
+{
+ "name": "ratchet/rfc6455",
+ "type": "library",
+ "description": "RFC6455 WebSocket protocol handler",
+ "keywords": ["WebSockets", "websocket", "RFC6455"],
+ "homepage": "http://socketo.me",
+ "license": "MIT",
+ "authors": [{
+ "name": "Chris Boden"
+ , "email": "cboden@gmail.com"
+ , "role": "Developer"
+ }],
+ "support": {
+ "forum": "https://groups.google.com/forum/#!forum/ratchet-php"
+ , "issues": "https://github.com/ratchetphp/RFC6455/issues"
+ , "irc": "irc://irc.freenode.org/reactphp"
+ },
+ "autoload": {
+ "psr-4": {
+ "Ratchet\\RFC6455\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.4.2",
+ "guzzlehttp/psr7": "^1.0"
+ },
+ "require-dev": {
+ "react/http": "^0.4.1",
+ "react/socket-client": "^0.4.3",
+ "phpunit/phpunit": "4.8.*"
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/phpunit.xml.dist b/assets/php/vendor/ratchet/rfc6455/phpunit.xml.dist
new file mode 100644
index 0000000..8f2e7d1
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/phpunit.xml.dist
@@ -0,0 +1,27 @@
+
+
+
+
+
+ tests
+
+ test/ab
+
+
+
+
+
+
+ ./src/
+
+
+
\ No newline at end of file
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php
new file mode 100644
index 0000000..70856df
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php
@@ -0,0 +1,53 @@
+verifier = new ResponseVerifier;
+
+ $this->defaultHeader = new Request('GET', '', [
+ 'Connection' => 'Upgrade'
+ , 'Upgrade' => 'websocket'
+ , 'Sec-WebSocket-Version' => $this->getVersion()
+ , 'User-Agent' => "Ratchet"
+ ]);
+ }
+
+ public function generateRequest(UriInterface $uri) {
+ return $this->defaultHeader->withUri($uri)
+ ->withHeader("Sec-WebSocket-Key", $this->generateKey());
+ }
+
+ public function validateResponse(RequestInterface $request, ResponseInterface $response) {
+ return $this->verifier->verifyAll($request, $response);
+ }
+
+ public function generateKey() {
+ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/=';
+ $charRange = strlen($chars) - 1;
+ $key = '';
+ for ($i = 0; $i < 16; $i++) {
+ $key .= $chars[mt_rand(0, $charRange)];
+ }
+
+ return base64_encode($key);
+ }
+
+ public function getVersion() {
+ return 13;
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php
new file mode 100644
index 0000000..c152eca
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php
@@ -0,0 +1,47 @@
+verifyMethod($request->getMethod());
+ $passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion());
+ $passes += (int)$this->verifyRequestURI($request->getUri()->getPath());
+ $passes += (int)$this->verifyHost($request->getHeader('Host'));
+ $passes += (int)$this->verifyUpgradeRequest($request->getHeader('Upgrade'));
+ $passes += (int)$this->verifyConnection($request->getHeader('Connection'));
+ $passes += (int)$this->verifyKey($request->getHeader('Sec-WebSocket-Key'));
+ $passes += (int)$this->verifyVersion($request->getHeader('Sec-WebSocket-Version'));
+
+ return (8 === $passes);
+ }
+
+ /**
+ * Test the HTTP method. MUST be "GET"
+ * @param string
+ * @return bool
+ */
+ public function verifyMethod($val) {
+ return ('get' === strtolower($val));
+ }
+
+ /**
+ * Test the HTTP version passed. MUST be 1.1 or greater
+ * @param string|int
+ * @return bool
+ */
+ public function verifyHTTPVersion($val) {
+ return (1.1 <= (double)$val);
+ }
+
+ /**
+ * @param string
+ * @return bool
+ */
+ public function verifyRequestURI($val) {
+ if ($val[0] !== '/') {
+ return false;
+ }
+
+ if (false !== strstr($val, '#')) {
+ return false;
+ }
+
+ if (!extension_loaded('mbstring')) {
+ return true;
+ }
+
+ return mb_check_encoding($val, 'US-ASCII');
+ }
+
+ /**
+ * @param array $hostHeader
+ * @return bool
+ * @todo Once I fix HTTP::getHeaders just verify this isn't NULL or empty...or maybe need to verify it's a valid domain??? Or should it equal $_SERVER['HOST'] ?
+ */
+ public function verifyHost(array $hostHeader) {
+ return (1 === count($hostHeader));
+ }
+
+ /**
+ * Verify the Upgrade request to WebSockets.
+ * @param array $upgradeHeader MUST equal "websocket"
+ * @return bool
+ */
+ public function verifyUpgradeRequest(array $upgradeHeader) {
+ return (1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0]));
+ }
+
+ /**
+ * Verify the Connection header
+ * @param array $connectionHeader MUST include "Upgrade"
+ * @return bool
+ */
+ public function verifyConnection(array $connectionHeader) {
+ foreach ($connectionHeader as $l) {
+ $upgrades = array_filter(
+ array_map('trim', array_map('strtolower', explode(',', $l))),
+ function ($x) {
+ return 'upgrade' === $x;
+ }
+ );
+ if (count($upgrades) > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This function verifies the nonce is valid (64 big encoded, 16 bytes random string)
+ * @param array $keyHeader
+ * @return bool
+ * @todo The spec says we don't need to base64_decode - can I just check if the length is 24 and not decode?
+ * @todo Check the spec to see what the encoding of the key could be
+ */
+ public function verifyKey(array $keyHeader) {
+ return (1 === count($keyHeader) && 16 === strlen(base64_decode($keyHeader[0])));
+ }
+
+ /**
+ * Verify the version passed matches this RFC
+ * @param string|int $versionHeader MUST equal 13|"13"
+ * @return bool
+ */
+ public function verifyVersion($versionHeader) {
+ return (1 === count($versionHeader) && static::VERSION === (int)$versionHeader[0]);
+ }
+
+ /**
+ * @todo Write logic for this method. See section 4.2.1.8
+ */
+ public function verifyProtocol($val) {
+ }
+
+ /**
+ * @todo Write logic for this method. See section 4.2.1.9
+ */
+ public function verifyExtensions($val) {
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php
new file mode 100644
index 0000000..de03f53
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php
@@ -0,0 +1,52 @@
+verifyStatus($response->getStatusCode());
+ $passes += (int)$this->verifyUpgrade($response->getHeader('Upgrade'));
+ $passes += (int)$this->verifyConnection($response->getHeader('Connection'));
+ $passes += (int)$this->verifySecWebSocketAccept(
+ $response->getHeader('Sec-WebSocket-Accept')
+ , $request->getHeader('Sec-WebSocket-Key')
+ );
+ $passes += (int)$this->verifySubProtocol(
+ $request->getHeader('Sec-WebSocket-Protocol')
+ , $response->getHeader('Sec-WebSocket-Protocol')
+ );
+
+ return (5 === $passes);
+ }
+
+ public function verifyStatus($status) {
+ return ((int)$status === 101);
+ }
+
+ public function verifyUpgrade(array $upgrade) {
+ return (in_array('websocket', array_map('strtolower', $upgrade)));
+ }
+
+ public function verifyConnection(array $connection) {
+ return (in_array('upgrade', array_map('strtolower', $connection)));
+ }
+
+ public function verifySecWebSocketAccept($swa, $key) {
+ return (
+ 1 === count($swa) &&
+ 1 === count($key) &&
+ $swa[0] === $this->sign($key[0])
+ );
+ }
+
+ public function sign($key) {
+ return base64_encode(sha1($key . NegotiatorInterface::GUID, true));
+ }
+
+ public function verifySubProtocol(array $requestHeader, array $responseHeader) {
+ return 0 === count($responseHeader) || count(array_intersect($responseHeader, $requestHeader)) > 0;
+ }
+}
\ No newline at end of file
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php
new file mode 100644
index 0000000..5a0073b
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php
@@ -0,0 +1,136 @@
+verifier = $requestVerifier;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isProtocol(RequestInterface $request) {
+ return $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVersionNumber() {
+ return RequestVerifier::VERSION;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handshake(RequestInterface $request) {
+ if (true !== $this->verifier->verifyMethod($request->getMethod())) {
+ return new Response(405, ['Allow' => 'GET']);
+ }
+
+ if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) {
+ return new Response(505);
+ }
+
+ if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) {
+ return new Response(400);
+ }
+
+ if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) {
+ return new Response(400);
+ }
+
+ $upgradeSuggestion = [
+ 'Connection' => 'Upgrade',
+ 'Upgrade' => 'websocket',
+ 'Sec-WebSocket-Version' => $this->getVersionNumber()
+ ];
+ if (count($this->_supportedSubProtocols) > 0) {
+ $upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', $this->_supportedSubProtocols);
+ }
+ if (true !== $this->verifier->verifyUpgradeRequest($request->getHeader('Upgrade'))) {
+ return new Response(426, $upgradeSuggestion, null, '1.1', 'Upgrade header MUST be provided');
+ }
+
+ if (true !== $this->verifier->verifyConnection($request->getHeader('Connection'))) {
+ return new Response(400, [], null, '1.1', 'Connection Upgrade MUST be requested');
+ }
+
+ if (true !== $this->verifier->verifyKey($request->getHeader('Sec-WebSocket-Key'))) {
+ return new Response(400, [], null, '1.1', 'Invalid Sec-WebSocket-Key');
+ }
+
+ if (true !== $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'))) {
+ return new Response(426, $upgradeSuggestion);
+ }
+
+ $headers = [];
+ $subProtocols = $request->getHeader('Sec-WebSocket-Protocol');
+ if (count($subProtocols) > 0 || (count($this->_supportedSubProtocols) > 0 && $this->_strictSubProtocols)) {
+ $subProtocols = array_map('trim', explode(',', implode(',', $subProtocols)));
+
+ $match = array_reduce($subProtocols, function($accumulator, $protocol) {
+ return $accumulator ?: (isset($this->_supportedSubProtocols[$protocol]) ? $protocol : null);
+ }, null);
+
+ if ($this->_strictSubProtocols && null === $match) {
+ return new Response(426, $upgradeSuggestion, null, '1.1', 'No Sec-WebSocket-Protocols requested supported');
+ }
+
+ if (null !== $match) {
+ $headers['Sec-WebSocket-Protocol'] = $match;
+ }
+ }
+
+ return new Response(101, array_merge($headers, [
+ 'Upgrade' => 'websocket'
+ , 'Connection' => 'Upgrade'
+ , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])
+ , 'X-Powered-By' => 'Ratchet'
+ ]));
+ }
+
+ /**
+ * Used when doing the handshake to encode the key, verifying client/server are speaking the same language
+ * @param string $key
+ * @return string
+ * @internal
+ */
+ public function sign($key) {
+ return base64_encode(sha1($key . static::GUID, true));
+ }
+
+ /**
+ * @param array $protocols
+ */
+ function setSupportedSubProtocols(array $protocols) {
+ $this->_supportedSubProtocols = array_flip($protocols);
+ }
+
+ /**
+ * If enabled and support for a subprotocol has been added handshake
+ * will not upgrade if a match between request and supported subprotocols
+ * @param boolean $enable
+ * @todo Consider extending this interface and moving this there.
+ * The spec does says the server can fail for this reason, but
+ * it is not a requirement. This is an implementation detail.
+ */
+ function setStrictSubProtocolCheck($enable) {
+ $this->_strictSubProtocols = (boolean)$enable;
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php
new file mode 100644
index 0000000..3d800e5
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php
@@ -0,0 +1,24 @@
+validCloseCodes = [
+ Frame::CLOSE_NORMAL,
+ Frame::CLOSE_GOING_AWAY,
+ Frame::CLOSE_PROTOCOL,
+ Frame::CLOSE_BAD_DATA,
+ Frame::CLOSE_BAD_PAYLOAD,
+ Frame::CLOSE_POLICY,
+ Frame::CLOSE_TOO_BIG,
+ Frame::CLOSE_MAND_EXT,
+ Frame::CLOSE_SRV_ERR,
+ ];
+ }
+
+ public function __invoke($val) {
+ return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes);
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php
new file mode 100644
index 0000000..18aa2e3
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php
@@ -0,0 +1,34 @@
+ $ufExceptionFactory
+ */
+ public function __construct($payload = null, $final = true, $opcode = 1, callable $ufExceptionFactory = null) {
+ $this->ufeg = $ufExceptionFactory ?: function($msg = '') {
+ return new \UnderflowException($msg);
+ };
+
+ if (null === $payload) {
+ return;
+ }
+
+ $this->defPayLen = strlen($payload);
+ $this->firstByte = ($final ? 128 : 0) + $opcode;
+ $this->secondByte = $this->defPayLen;
+ $this->isCoalesced = true;
+
+ $ext = '';
+ if ($this->defPayLen > 65535) {
+ $ext = pack('NN', 0, $this->defPayLen);
+ $this->secondByte = 127;
+ } elseif ($this->defPayLen > 125) {
+ $ext = pack('n', $this->defPayLen);
+ $this->secondByte = 126;
+ }
+
+ $this->data = chr($this->firstByte) . chr($this->secondByte) . $ext . $payload;
+ $this->bytesRecvd = 2 + strlen($ext) + $this->defPayLen;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isCoalesced() {
+ if (true === $this->isCoalesced) {
+ return true;
+ }
+
+ try {
+ $payload_length = $this->getPayloadLength();
+ $payload_start = $this->getPayloadStartingByte();
+ } catch (\UnderflowException $e) {
+ return false;
+ }
+
+ $this->isCoalesced = $this->bytesRecvd >= $payload_length + $payload_start;
+
+ return $this->isCoalesced;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addBuffer($buf) {
+ $len = strlen($buf);
+
+ $this->data .= $buf;
+ $this->bytesRecvd += $len;
+
+ if ($this->firstByte === -1 && $this->bytesRecvd !== 0) {
+ $this->firstByte = ord($this->data[0]);
+ }
+
+ if ($this->secondByte === -1 && $this->bytesRecvd >= 2) {
+ $this->secondByte = ord($this->data[1]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFinal() {
+ if (-1 === $this->firstByte) {
+ throw call_user_func($this->ufeg, 'Not enough bytes received to determine if this is the final frame in message');
+ }
+
+ return 128 === ($this->firstByte & 128);
+ }
+
+ /**
+ * @return boolean
+ * @throws \UnderflowException
+ */
+ public function getRsv1() {
+ if (-1 === $this->firstByte) {
+ throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit');
+ }
+
+ return 64 === ($this->firstByte & 64);
+ }
+
+ /**
+ * @return boolean
+ * @throws \UnderflowException
+ */
+ public function getRsv2() {
+ if (-1 === $this->firstByte) {
+ throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit');
+ }
+
+ return 32 === ($this->firstByte & 32);
+ }
+
+ /**
+ * @return boolean
+ * @throws \UnderflowException
+ */
+ public function getRsv3() {
+ if (-1 === $this->firstByte) {
+ throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit');
+ }
+
+ return 16 === ($this->firstByte & 16);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isMasked() {
+ if (-1 === $this->secondByte) {
+ throw call_user_func($this->ufeg, "Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set");
+ }
+
+ return 128 === ($this->secondByte & 128);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMaskingKey() {
+ if (!$this->isMasked()) {
+ return '';
+ }
+
+ $start = 1 + $this->getNumPayloadBytes();
+
+ if ($this->bytesRecvd < $start + static::MASK_LENGTH) {
+ throw call_user_func($this->ufeg, 'Not enough data buffered to calculate the masking key');
+ }
+
+ return substr($this->data, $start, static::MASK_LENGTH);
+ }
+
+ /**
+ * Create a 4 byte masking key
+ * @return string
+ */
+ public function generateMaskingKey() {
+ $mask = '';
+
+ for ($i = 1; $i <= static::MASK_LENGTH; $i++) {
+ $mask .= chr(rand(32, 126));
+ }
+
+ return $mask;
+ }
+
+ /**
+ * Apply a mask to the payload
+ * @param string|null If NULL is passed a masking key will be generated
+ * @throws \OutOfBoundsException
+ * @throws \InvalidArgumentException If there is an issue with the given masking key
+ * @return Frame
+ */
+ public function maskPayload($maskingKey = null) {
+ if (null === $maskingKey) {
+ $maskingKey = $this->generateMaskingKey();
+ }
+
+ if (static::MASK_LENGTH !== strlen($maskingKey)) {
+ throw new \InvalidArgumentException("Masking key must be " . static::MASK_LENGTH ." characters");
+ }
+
+ if (extension_loaded('mbstring') && true !== mb_check_encoding($maskingKey, 'US-ASCII')) {
+ throw new \OutOfBoundsException("Masking key MUST be ASCII");
+ }
+
+ $this->unMaskPayload();
+
+ $this->secondByte = $this->secondByte | 128;
+ $this->data[1] = chr($this->secondByte);
+
+ $this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0);
+
+ $this->bytesRecvd += static::MASK_LENGTH;
+ $this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength());
+
+ return $this;
+ }
+
+ /**
+ * Remove a mask from the payload
+ * @throws \UnderFlowException If the frame is not coalesced
+ * @return Frame
+ */
+ public function unMaskPayload() {
+ if (!$this->isCoalesced()) {
+ throw call_user_func($this->ufeg, 'Frame must be coalesced before applying mask');
+ }
+
+ if (!$this->isMasked()) {
+ return $this;
+ }
+
+ $maskingKey = $this->getMaskingKey();
+
+ $this->secondByte = $this->secondByte & ~128;
+ $this->data[1] = chr($this->secondByte);
+
+ $this->data = substr_replace($this->data, '', $this->getNumPayloadBytes() + 1, static::MASK_LENGTH);
+
+ $this->bytesRecvd -= static::MASK_LENGTH;
+ $this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength());
+
+ return $this;
+ }
+
+ /**
+ * Apply a mask to a string or the payload of the instance
+ * @param string $maskingKey The 4 character masking key to be applied
+ * @param string|null $payload A string to mask or null to use the payload
+ * @throws \UnderflowException If using the payload but enough hasn't been buffered
+ * @return string The masked string
+ */
+ public function applyMask($maskingKey, $payload = null) {
+ if (null === $payload) {
+ if (!$this->isCoalesced()) {
+ throw call_user_func($this->ufeg, 'Frame must be coalesced to apply a mask');
+ }
+
+ $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength());
+ }
+
+ $len = strlen($payload);
+
+ if (0 === $len) {
+ return '';
+ }
+
+ return $payload ^ str_pad('', $len, $maskingKey, STR_PAD_RIGHT);
+
+ // TODO: Remove this before publish - keeping methods here to compare performance (above is faster but need control against v0.3.3)
+
+ $applied = '';
+ for ($i = 0, $len = strlen($payload); $i < $len; $i++) {
+ $applied .= $payload[$i] ^ $maskingKey[$i % static::MASK_LENGTH];
+ }
+
+ return $applied;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOpcode() {
+ if (-1 === $this->firstByte) {
+ throw call_user_func($this->ufeg, 'Not enough bytes received to determine opcode');
+ }
+
+ return ($this->firstByte & ~240);
+ }
+
+ /**
+ * Gets the decimal value of bits 9 (10th) through 15 inclusive
+ * @return int
+ * @throws \UnderflowException If the buffer doesn't have enough data to determine this
+ */
+ protected function getFirstPayloadVal() {
+ if (-1 === $this->secondByte) {
+ throw call_user_func($this->ufeg, 'Not enough bytes received');
+ }
+
+ return $this->secondByte & 127;
+ }
+
+ /**
+ * @return int (7|23|71) Number of bits defined for the payload length in the fame
+ * @throws \UnderflowException
+ */
+ protected function getNumPayloadBits() {
+ if (-1 === $this->secondByte) {
+ throw call_user_func($this->ufeg, 'Not enough bytes received');
+ }
+
+ // By default 7 bits are used to describe the payload length
+ // These are bits 9 (10th) through 15 inclusive
+ $bits = 7;
+
+ // Get the value of those bits
+ $check = $this->getFirstPayloadVal();
+
+ // If the value is 126 the 7 bits plus the next 16 are used to describe the payload length
+ if ($check >= 126) {
+ $bits += 16;
+ }
+
+ // If the value of the initial payload length are is 127 an additional 48 bits are used to describe length
+ // Note: The documentation specifies the length is to be 63 bits, but I think that's a typo and is 64 (16+48)
+ if ($check === 127) {
+ $bits += 48;
+ }
+
+ return $bits;
+ }
+
+ /**
+ * This just returns the number of bytes used in the frame to describe the payload length (as opposed to # of bits)
+ * @see getNumPayloadBits
+ */
+ protected function getNumPayloadBytes() {
+ return (1 + $this->getNumPayloadBits()) / 8;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPayloadLength() {
+ if ($this->defPayLen !== -1) {
+ return $this->defPayLen;
+ }
+
+ $this->defPayLen = $this->getFirstPayloadVal();
+ if ($this->defPayLen <= 125) {
+ return $this->getPayloadLength();
+ }
+
+ $byte_length = $this->getNumPayloadBytes();
+ if ($this->bytesRecvd < 1 + $byte_length) {
+ $this->defPayLen = -1;
+ throw call_user_func($this->ufeg, 'Not enough data buffered to determine payload length');
+ }
+
+ $len = 0;
+ for ($i = 2; $i <= $byte_length; $i++) {
+ $len <<= 8;
+ $len += ord($this->data[$i]);
+ }
+
+ $this->defPayLen = $len;
+
+ return $this->getPayloadLength();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPayloadStartingByte() {
+ return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @todo Consider not checking mask, always returning the payload, masked or not
+ */
+ public function getPayload() {
+ if (!$this->isCoalesced()) {
+ throw call_user_func($this->ufeg, 'Can not return partial message');
+ }
+
+ return $this->__toString();
+ }
+
+ /**
+ * Get the raw contents of the frame
+ * @todo This is untested, make sure the substr is right - trying to return the frame w/o the overflow
+ */
+ public function getContents() {
+ return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength());
+ }
+
+ public function __toString() {
+ $payload = (string)substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength());
+
+ if ($this->isMasked()) {
+ $payload = $this->applyMask($this->getMaskingKey(), $payload);
+ }
+
+ return $payload;
+ }
+
+ /**
+ * Sometimes clients will concatenate more than one frame over the wire
+ * This method will take the extra bytes off the end and return them
+ * @return string
+ */
+ public function extractOverflow() {
+ if ($this->isCoalesced()) {
+ $endPoint = $this->getPayloadLength();
+ $endPoint += $this->getPayloadStartingByte();
+
+ if ($this->bytesRecvd > $endPoint) {
+ $overflow = substr($this->data, $endPoint);
+ $this->data = substr($this->data, 0, $endPoint);
+
+ return $overflow;
+ }
+ }
+
+ return '';
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php
new file mode 100644
index 0000000..dc24091
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php
@@ -0,0 +1,38 @@
+_frames = new \SplDoublyLinkedList;
+ }
+
+ public function getIterator() {
+ return $this->_frames;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function count() {
+ return count($this->_frames);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isCoalesced() {
+ if (count($this->_frames) == 0) {
+ return false;
+ }
+
+ $last = $this->_frames->top();
+
+ return ($last->isCoalesced() && $last->isFinal());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFrame(FrameInterface $fragment) {
+ $this->_frames->push($fragment);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOpcode() {
+ if (count($this->_frames) == 0) {
+ throw new \UnderflowException('No frames have been added to this message');
+ }
+
+ return $this->_frames->bottom()->getOpcode();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPayloadLength() {
+ $len = 0;
+
+ foreach ($this->_frames as $frame) {
+ try {
+ $len += $frame->getPayloadLength();
+ } catch (\UnderflowException $e) {
+ // Not an error, want the current amount buffered
+ }
+ }
+
+ return $len;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPayload() {
+ if (!$this->isCoalesced()) {
+ throw new \UnderflowException('Message has not been put back together yet');
+ }
+
+ return $this->__toString();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContents() {
+ if (!$this->isCoalesced()) {
+ throw new \UnderflowException("Message has not been put back together yet");
+ }
+
+ $buffer = '';
+
+ foreach ($this->_frames as $frame) {
+ $buffer .= $frame->getContents();
+ }
+
+ return $buffer;
+ }
+
+ public function __toString() {
+ $buffer = '';
+
+ foreach ($this->_frames as $frame) {
+ $buffer .= $frame->getPayload();
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isBinary() {
+ if ($this->_frames->isEmpty()) {
+ throw new \UnderflowException('Not enough data has been received to determine if message is binary');
+ }
+
+ return Frame::OP_BINARY === $this->_frames->bottom()->getOpcode();
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php
new file mode 100644
index 0000000..07ff4f1
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php
@@ -0,0 +1,231 @@
+closeFrameChecker = $frameChecker;
+ $this->checkForMask = (bool)$expectMask;
+
+ $this->exceptionFactory ?: $this->exceptionFactory = function($msg) {
+ return new \UnderflowException($msg);
+ };
+
+ $this->onMessage = $onMessage;
+ $this->onControl = $onControl ?: function() {};
+ }
+
+ public function onData($data) {
+ while (strlen($data) > 0) {
+ $data = $this->processData($data);
+ }
+ }
+
+ /**
+ * @param string $data
+ * @return null
+ */
+ private function processData($data) {
+ $this->messageBuffer ?: $this->messageBuffer = $this->newMessage();
+ $this->frameBuffer ?: $this->frameBuffer = $this->newFrame();
+
+ $this->frameBuffer->addBuffer($data);
+ if (!$this->frameBuffer->isCoalesced()) {
+ return '';
+ }
+
+ $onMessage = $this->onMessage;
+ $onControl = $this->onControl;
+
+ $this->frameBuffer = $this->frameCheck($this->frameBuffer);
+
+ $overflow = $this->frameBuffer->extractOverflow();
+ $this->frameBuffer->unMaskPayload();
+
+ $opcode = $this->frameBuffer->getOpcode();
+
+ if ($opcode > 2) {
+ $onControl($this->frameBuffer);
+
+ if (Frame::OP_CLOSE === $opcode) {
+ return '';
+ }
+ } else {
+ $this->messageBuffer->addFrame($this->frameBuffer);
+ }
+
+ $this->frameBuffer = null;
+
+ if ($this->messageBuffer->isCoalesced()) {
+ $msgCheck = $this->checkMessage($this->messageBuffer);
+ if (true !== $msgCheck) {
+ $onControl($this->newCloseFrame($msgCheck, 'Ratchet detected an invalid UTF-8 payload'));
+ } else {
+ $onMessage($this->messageBuffer);
+ }
+
+ $this->messageBuffer = null;
+ }
+
+ return $overflow;
+ }
+
+ /**
+ * Check a frame to be added to the current message buffer
+ * @param \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface $frame
+ * @return \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface
+ */
+ public function frameCheck(FrameInterface $frame) {
+ if (false !== $frame->getRsv1() ||
+ false !== $frame->getRsv2() ||
+ false !== $frame->getRsv3()
+ ) {
+ return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid reserve code');
+ }
+
+ if ($this->checkForMask && !$frame->isMasked()) {
+ return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an incorrect frame mask');
+ }
+
+ $opcode = $frame->getOpcode();
+
+ if ($opcode > 2) {
+ if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) {
+ return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected a mismatch between final bit and indicated payload length');
+ }
+
+ switch ($opcode) {
+ case Frame::OP_CLOSE:
+ $closeCode = 0;
+
+ $bin = $frame->getPayload();
+
+ if (empty($bin)) {
+ return $this->newCloseFrame(Frame::CLOSE_NORMAL);
+ }
+
+ if (strlen($bin) === 1) {
+ return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid close code');
+ }
+
+ if (strlen($bin) >= 2) {
+ list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2)));
+ }
+
+ $checker = $this->closeFrameChecker;
+ if (!$checker($closeCode)) {
+ return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid close code');
+ }
+
+ if (!$this->checkUtf8(substr($bin, 2))) {
+ return $this->newCloseFrame(Frame::CLOSE_BAD_PAYLOAD, 'Ratchet detected an invalid UTF-8 payload in the close reason');
+ }
+
+ return $frame;
+ break;
+ case Frame::OP_PING:
+ case Frame::OP_PONG:
+ break;
+ default:
+ return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid OP code');
+ break;
+ }
+
+ return $frame;
+ }
+
+ if (Frame::OP_CONTINUE === $frame->getOpcode() && 0 === count($this->messageBuffer)) {
+ return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected the first frame of a message was a continue');
+ }
+
+ if (count($this->messageBuffer) > 0 && Frame::OP_CONTINUE !== $frame->getOpcode()) {
+ return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected invalid OP code when expecting continue frame');
+ }
+
+ return $frame;
+ }
+
+ /**
+ * Determine if a message is valid
+ * @param \Ratchet\RFC6455\Messaging\MessageInterface
+ * @return bool|int true if valid - false if incomplete - int of recommended close code
+ */
+ public function checkMessage(MessageInterface $message) {
+ if (!$message->isBinary()) {
+ if (!$this->checkUtf8($message->getPayload())) {
+ return Frame::CLOSE_BAD_PAYLOAD;
+ }
+ }
+
+ return true;
+ }
+
+ private function checkUtf8($string) {
+ if (extension_loaded('mbstring')) {
+ return mb_check_encoding($string, 'UTF-8');
+ }
+
+ return preg_match('//u', $string);
+ }
+
+ /**
+ * @return \Ratchet\RFC6455\Messaging\MessageInterface
+ */
+ public function newMessage() {
+ return new Message;
+ }
+
+ /**
+ * @param string|null $payload
+ * @param bool|null $final
+ * @param int|null $opcode
+ * @return \Ratchet\RFC6455\Messaging\FrameInterface
+ */
+ public function newFrame($payload = null, $final = null, $opcode = null) {
+ return new Frame($payload, $final, $opcode, $this->exceptionFactory);
+ }
+
+ public function newCloseFrame($code, $reason = '') {
+ return $this->newFrame(pack('n', $code) . $reason, true, Frame::OP_CLOSE);
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php
new file mode 100644
index 0000000..fd7212e
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php
@@ -0,0 +1,20 @@
+markTestSkipped('Autobahn TestSuite results not found');
+ }
+
+ $resultsJson = file_get_contents($fileName);
+ $results = json_decode($resultsJson);
+ $agentName = array_keys(get_object_vars($results))[0];
+
+ foreach ($results->$agentName as $name => $result) {
+ if ($result->behavior === "INFORMATIONAL") {
+ continue;
+ }
+
+ $this->assertTrue(in_array($result->behavior, ["OK", "NON-STRICT"]), "Autobahn test case " . $name . " in " . $fileName);
+ }
+ }
+
+ public function testAutobahnClientResults() {
+ $this->verifyAutobahnResults(__DIR__ . '/ab/reports/clients/index.json');
+ }
+
+ public function testAutobahnServerResults() {
+ $this->verifyAutobahnResults(__DIR__ . '/ab/reports/servers/index.json');
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/clientRunner.php b/assets/php/vendor/ratchet/rfc6455/tests/ab/clientRunner.php
new file mode 100644
index 0000000..0c5578a
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/clientRunner.php
@@ -0,0 +1,228 @@
+createCached('8.8.8.8', $loop);
+
+$factory = new \React\SocketClient\Connector($loop, $dnsResolver);
+
+function echoStreamerFactory($conn)
+{
+ return new \Ratchet\RFC6455\Messaging\MessageBuffer(
+ new \Ratchet\RFC6455\Messaging\CloseFrameChecker,
+ function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
+ /** @var Frame $frame */
+ foreach ($msg as $frame) {
+ $frame->maskPayload();
+ }
+ $conn->write($msg->getContents());
+ },
+ function (\Ratchet\RFC6455\Messaging\FrameInterface $frame) use ($conn) {
+ switch ($frame->getOpcode()) {
+ case Frame::OP_PING:
+ return $conn->write((new Frame($frame->getPayload(), true, Frame::OP_PONG))->maskPayload()->getContents());
+ break;
+ case Frame::OP_CLOSE:
+ return $conn->end((new Frame($frame->getPayload(), true, Frame::OP_CLOSE))->maskPayload()->getContents());
+ break;
+ }
+ },
+ false
+ );
+}
+
+function getTestCases() {
+ global $factory;
+ global $testServer;
+
+ $deferred = new Deferred();
+
+ $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) {
+ $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator();
+ $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001/getCaseCount'));
+
+ $rawResponse = "";
+ $response = null;
+
+ /** @var \Ratchet\RFC6455\Messaging\Streaming\MessageBuffer $ms */
+ $ms = null;
+
+ $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) {
+ if ($response === null) {
+ $rawResponse .= $data;
+ $pos = strpos($rawResponse, "\r\n\r\n");
+ if ($pos) {
+ $data = substr($rawResponse, $pos + 4);
+ $rawResponse = substr($rawResponse, 0, $pos + 4);
+ $response = \GuzzleHttp\Psr7\parse_response($rawResponse);
+
+ if (!$cn->validateResponse($cnRequest, $response)) {
+ $stream->end();
+ $deferred->reject();
+ } else {
+ $ms = new \Ratchet\RFC6455\Messaging\MessageBuffer(
+ new \Ratchet\RFC6455\Messaging\CloseFrameChecker,
+ function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($deferred, $stream) {
+ $deferred->resolve($msg->getPayload());
+ $stream->close();
+ },
+ null,
+ false
+ );
+ }
+ }
+ }
+
+ // feed the message streamer
+ if ($ms) {
+ $ms->onData($data);
+ }
+ });
+
+ $stream->write(\GuzzleHttp\Psr7\str($cnRequest));
+ });
+
+ return $deferred->promise();
+}
+
+function runTest($case)
+{
+ global $factory;
+ global $testServer;
+
+ $casePath = "/runCase?case={$case}&agent=" . AGENT;
+
+ $deferred = new Deferred();
+
+ $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred, $casePath, $case) {
+ $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator();
+ $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $casePath));
+
+ $rawResponse = "";
+ $response = null;
+
+ $ms = null;
+
+ $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) {
+ if ($response === null) {
+ $rawResponse .= $data;
+ $pos = strpos($rawResponse, "\r\n\r\n");
+ if ($pos) {
+ $data = substr($rawResponse, $pos + 4);
+ $rawResponse = substr($rawResponse, 0, $pos + 4);
+ $response = \GuzzleHttp\Psr7\parse_response($rawResponse);
+
+ if (!$cn->validateResponse($cnRequest, $response)) {
+ $stream->end();
+ $deferred->reject();
+ } else {
+ $ms = echoStreamerFactory($stream);
+ }
+ }
+ }
+
+ // feed the message streamer
+ if ($ms) {
+ $ms->onData($data);
+ }
+ });
+
+ $stream->on('close', function () use ($deferred) {
+ $deferred->resolve();
+ });
+
+ $stream->write(\GuzzleHttp\Psr7\str($cnRequest));
+ });
+
+ return $deferred->promise();
+}
+
+function createReport() {
+ global $factory;
+ global $testServer;
+
+ $deferred = new Deferred();
+
+ $factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) {
+ $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true";
+ $cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator();
+ $cnRequest = $cn->generateRequest(new Uri('ws://127.0.0.1:9001' . $reportPath));
+
+ $rawResponse = "";
+ $response = null;
+
+ /** @var \Ratchet\RFC6455\Messaging\MessageBuffer $ms */
+ $ms = null;
+
+ $stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context, $cnRequest) {
+ if ($response === null) {
+ $rawResponse .= $data;
+ $pos = strpos($rawResponse, "\r\n\r\n");
+ if ($pos) {
+ $data = substr($rawResponse, $pos + 4);
+ $rawResponse = substr($rawResponse, 0, $pos + 4);
+ $response = \GuzzleHttp\Psr7\parse_response($rawResponse);
+
+ if (!$cn->validateResponse($cnRequest, $response)) {
+ $stream->end();
+ $deferred->reject();
+ } else {
+ $ms = new \Ratchet\RFC6455\Messaging\MessageBuffer(
+ new \Ratchet\RFC6455\Messaging\CloseFrameChecker,
+ function (\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($deferred, $stream) {
+ $deferred->resolve($msg->getPayload());
+ $stream->close();
+ },
+ null,
+ false
+ );
+ }
+ }
+ }
+
+ // feed the message streamer
+ if ($ms) {
+ $ms->onData($data);
+ }
+ });
+
+ $stream->write(\GuzzleHttp\Psr7\str($cnRequest));
+ });
+
+ return $deferred->promise();
+}
+
+
+$testPromises = [];
+
+getTestCases()->then(function ($count) use ($loop) {
+ $allDeferred = new Deferred();
+
+ $runNextCase = function () use (&$i, &$runNextCase, $count, $allDeferred) {
+ $i++;
+ if ($i > $count) {
+ $allDeferred->resolve();
+ return;
+ }
+ runTest($i)->then($runNextCase);
+ };
+
+ $i = 0;
+ $runNextCase();
+
+ $allDeferred->promise()->then(function () {
+ createReport();
+ });
+});
+
+$loop->run();
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingclient.json b/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingclient.json
new file mode 100644
index 0000000..d2fd0d0
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingclient.json
@@ -0,0 +1,14 @@
+{
+ "options": {
+ "failByDrop": false
+ }
+ , "outdir": "./reports/servers"
+ , "servers": [{
+ "agent": "RatchetRFC/0.1.0"
+ , "url": "ws://localhost:9001"
+ , "options": {"version": 18}
+ }]
+ , "cases": ["*"]
+ , "exclude-cases": ["6.4.*", "12.*","13.*"]
+ , "exclude-agent-cases": {}
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingserver.json b/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingserver.json
new file mode 100644
index 0000000..0422560
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/fuzzingserver.json
@@ -0,0 +1,10 @@
+{
+ "url": "ws://127.0.0.1:9001"
+ , "options": {
+ "failByDrop": false
+ }
+ , "outdir": "./reports/clients"
+ , "cases": ["*"]
+ , "exclude-cases": ["6.4.*", "12.*", "13.*"]
+ , "exclude-agent-cases": {}
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/run_ab_tests.sh b/assets/php/vendor/ratchet/rfc6455/tests/ab/run_ab_tests.sh
new file mode 100644
index 0000000..8fa9ced
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/run_ab_tests.sh
@@ -0,0 +1,11 @@
+cd tests/ab
+
+wstest -m fuzzingserver -s fuzzingserver.json &
+sleep 5
+php clientRunner.php
+
+sleep 2
+
+php startServer.php &
+sleep 3
+wstest -m fuzzingclient -s fuzzingclient.json
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/ab/startServer.php b/assets/php/vendor/ratchet/rfc6455/tests/ab/startServer.php
new file mode 100644
index 0000000..b256ec2
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/ab/startServer.php
@@ -0,0 +1,55 @@
+on('request', function (\React\Http\Request $request, \React\Http\Response $response) use ($negotiator, $closeFrameChecker, $uException) {
+ $psrRequest = new \GuzzleHttp\Psr7\Request($request->getMethod(), $request->getPath(), $request->getHeaders());
+
+ $negotiatorResponse = $negotiator->handshake($psrRequest);
+
+ $response->writeHead(
+ $negotiatorResponse->getStatusCode(),
+ array_merge(
+ $negotiatorResponse->getHeaders(),
+ ["Content-Length" => "0"]
+ )
+ );
+
+ if ($negotiatorResponse->getStatusCode() !== 101) {
+ $response->end();
+ return;
+ }
+
+ $parser = new \Ratchet\RFC6455\Messaging\MessageBuffer($closeFrameChecker, function(MessageInterface $message) use ($response) {
+ $response->write($message->getContents());
+ }, function(FrameInterface $frame) use ($response, &$parser) {
+ switch ($frame->getOpCode()) {
+ case Frame::OP_CLOSE:
+ $response->end($frame->getContents());
+ break;
+ case Frame::OP_PING:
+ $response->write($parser->newFrame($frame->getPayload(), true, Frame::OP_PONG)->getContents());
+ break;
+ }
+ }, true, function() use ($uException) {
+ return $uException;
+ });
+
+ $request->on('data', [$parser, 'onData']);
+});
+
+$socket->listen(9001, '0.0.0.0');
+$loop->run();
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/bootstrap.php b/assets/php/vendor/ratchet/rfc6455/tests/bootstrap.php
new file mode 100644
index 0000000..511b041
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/bootstrap.php
@@ -0,0 +1,19 @@
+addPsr4('Ratchet\\RFC6455\\Test\\', __DIR__);
+ break;
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/RequestVerifierTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/RequestVerifierTest.php
new file mode 100644
index 0000000..239de33
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/RequestVerifierTest.php
@@ -0,0 +1,177 @@
+_v = new RequestVerifier();
+ }
+
+ public static function methodProvider() {
+ return array(
+ array(true, 'GET'),
+ array(true, 'get'),
+ array(true, 'Get'),
+ array(false, 'POST'),
+ array(false, 'DELETE'),
+ array(false, 'PUT'),
+ array(false, 'PATCH')
+ );
+ }
+ /**
+ * @dataProvider methodProvider
+ */
+ public function testMethodMustBeGet($result, $in) {
+ $this->assertEquals($result, $this->_v->verifyMethod($in));
+ }
+
+ public static function httpVersionProvider() {
+ return array(
+ array(true, 1.1),
+ array(true, '1.1'),
+ array(true, 1.2),
+ array(true, '1.2'),
+ array(true, 2),
+ array(true, '2'),
+ array(true, '2.0'),
+ array(false, '1.0'),
+ array(false, 1),
+ array(false, '0.9'),
+ array(false, ''),
+ array(false, 'hello')
+ );
+ }
+
+ /**
+ * @dataProvider httpVersionProvider
+ */
+ public function testHttpVersionIsAtLeast1Point1($expected, $in) {
+ $this->assertEquals($expected, $this->_v->verifyHTTPVersion($in));
+ }
+
+ public static function uRIProvider() {
+ return array(
+ array(true, '/chat'),
+ array(true, '/hello/world?key=val'),
+ array(false, '/chat#bad'),
+ array(false, 'nope'),
+ array(false, '/ ಠ_ಠ '),
+ array(false, '/✖')
+ );
+ }
+
+ /**
+ * @dataProvider URIProvider
+ */
+ public function testRequestUri($expected, $in) {
+ $this->assertEquals($expected, $this->_v->verifyRequestURI($in));
+ }
+
+ public static function hostProvider() {
+ return array(
+ array(true, ['server.example.com']),
+ array(false, [])
+ );
+ }
+
+ /**
+ * @dataProvider HostProvider
+ */
+ public function testVerifyHostIsSet($expected, $in) {
+ $this->assertEquals($expected, $this->_v->verifyHost($in));
+ }
+
+ public static function upgradeProvider() {
+ return array(
+ array(true, ['websocket']),
+ array(true, ['Websocket']),
+ array(true, ['webSocket']),
+ array(false, []),
+ array(false, [''])
+ );
+ }
+
+ /**
+ * @dataProvider upgradeProvider
+ */
+ public function testVerifyUpgradeIsWebSocket($expected, $val) {
+ $this->assertEquals($expected, $this->_v->verifyUpgradeRequest($val));
+ }
+
+ public static function connectionProvider() {
+ return array(
+ array(true, ['Upgrade']),
+ array(true, ['upgrade']),
+ array(true, ['keep-alive', 'Upgrade']),
+ array(true, ['Upgrade', 'keep-alive']),
+ array(true, ['keep-alive', 'Upgrade', 'something']),
+ // as seen in Firefox 47.0.1 - see https://github.com/ratchetphp/RFC6455/issues/14
+ array(true, ['keep-alive, Upgrade']),
+ array(true, ['Upgrade, keep-alive']),
+ array(true, ['keep-alive, Upgrade, something']),
+ array(true, ['keep-alive, Upgrade', 'something']),
+ array(false, ['']),
+ array(false, [])
+ );
+ }
+
+ /**
+ * @dataProvider connectionProvider
+ */
+ public function testConnectionHeaderVerification($expected, $val) {
+ $this->assertEquals($expected, $this->_v->verifyConnection($val));
+ }
+
+ public static function keyProvider() {
+ return array(
+ array(true, ['hkfa1L7uwN6DCo4IS3iWAw==']),
+ array(true, ['765vVoQpKSGJwPzJIMM2GA==']),
+ array(true, ['AQIDBAUGBwgJCgsMDQ4PEC==']),
+ array(true, ['axa2B/Yz2CdpfQAY2Q5P7w==']),
+ array(false, [0]),
+ array(false, ['Hello World']),
+ array(false, ['1234567890123456']),
+ array(false, ['123456789012345678901234']),
+ array(true, [base64_encode('UTF8allthngs+✓')]),
+ array(true, ['dGhlIHNhbXBsZSBub25jZQ==']),
+ array(false, []),
+ array(false, ['dGhlIHNhbXBsZSBub25jZQ==', 'Some other value']),
+ array(false, ['Some other value', 'dGhlIHNhbXBsZSBub25jZQ=='])
+ );
+ }
+
+ /**
+ * @dataProvider keyProvider
+ */
+ public function testKeyIsBase64Encoded16BitNonce($expected, $val) {
+ $this->assertEquals($expected, $this->_v->verifyKey($val));
+ }
+
+ public static function versionProvider() {
+ return array(
+ array(true, [13]),
+ array(true, ['13']),
+ array(false, [12]),
+ array(false, [14]),
+ array(false, ['14']),
+ array(false, ['hi']),
+ array(false, ['']),
+ array(false, [])
+ );
+ }
+
+ /**
+ * @dataProvider versionProvider
+ */
+ public function testVersionEquals13($expected, $in) {
+ $this->assertEquals($expected, $this->_v->verifyVersion($in));
+ }
+}
\ No newline at end of file
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ResponseVerifierTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ResponseVerifierTest.php
new file mode 100644
index 0000000..312930e
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ResponseVerifierTest.php
@@ -0,0 +1,34 @@
+_v = new ResponseVerifier;
+ }
+
+ public static function subProtocolsProvider() {
+ return [
+ [true, ['a'], ['a']]
+ , [true, ['b', 'a'], ['c', 'd', 'a']]
+ , [false, ['a', 'b', 'c'], ['d']]
+ , [true, [], []]
+ , [true, ['a', 'b'], []]
+ ];
+ }
+
+ /**
+ * @dataProvider subProtocolsProvider
+ */
+ public function testVerifySubProtocol($expected, $response, $request) {
+ $this->assertEquals($expected, $this->_v->verifySubProtocol($response, $request));
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ServerNegotiatorTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ServerNegotiatorTest.php
new file mode 100644
index 0000000..9c9aa8d
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Handshake/ServerNegotiatorTest.php
@@ -0,0 +1,175 @@
+handshake($request);
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ $this->assertEquals(426, $response->getStatusCode());
+ $this->assertEquals('Upgrade header MUST be provided', $response->getReasonPhrase());
+ $this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
+ $this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
+ $this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version'));
+ }
+
+ public function testNoConnectionUpgradeRequested() {
+ $negotiator = new ServerNegotiator(new RequestVerifier());
+
+ $requestText = 'GET / HTTP/1.1
+Host: 127.0.0.1:6789
+Connection: keep-alive
+Pragma: no-cache
+Cache-Control: no-cache
+Upgrade: websocket
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Accept-Encoding: gzip, deflate, sdch, br
+Accept-Language: en-US,en;q=0.8';
+
+ $request = \GuzzleHttp\Psr7\parse_request($requestText);
+
+ $response = $negotiator->handshake($request);
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ $this->assertEquals(400, $response->getStatusCode());
+ $this->assertEquals('Connection Upgrade MUST be requested', $response->getReasonPhrase());
+ }
+
+ public function testInvalidSecWebsocketKey() {
+ $negotiator = new ServerNegotiator(new RequestVerifier());
+
+ $requestText = 'GET / HTTP/1.1
+Host: 127.0.0.1:6789
+Connection: Upgrade
+Pragma: no-cache
+Cache-Control: no-cache
+Upgrade: websocket
+Sec-WebSocket-Key: 12345
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Accept-Encoding: gzip, deflate, sdch, br
+Accept-Language: en-US,en;q=0.8';
+
+ $request = \GuzzleHttp\Psr7\parse_request($requestText);
+
+ $response = $negotiator->handshake($request);
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ $this->assertEquals(400, $response->getStatusCode());
+ $this->assertEquals('Invalid Sec-WebSocket-Key', $response->getReasonPhrase());
+ }
+
+ public function testInvalidSecWebsocketVersion() {
+ $negotiator = new ServerNegotiator(new RequestVerifier());
+
+ $requestText = 'GET / HTTP/1.1
+Host: 127.0.0.1:6789
+Connection: Upgrade
+Pragma: no-cache
+Cache-Control: no-cache
+Upgrade: websocket
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Accept-Encoding: gzip, deflate, sdch, br
+Accept-Language: en-US,en;q=0.8';
+
+ $request = \GuzzleHttp\Psr7\parse_request($requestText);
+
+ $response = $negotiator->handshake($request);
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ $this->assertEquals(426, $response->getStatusCode());
+ $this->assertEquals('Upgrade Required', $response->getReasonPhrase());
+ $this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
+ $this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
+ $this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version'));
+ }
+
+ public function testBadSubprotocolResponse() {
+ $negotiator = new ServerNegotiator(new RequestVerifier());
+ $negotiator->setStrictSubProtocolCheck(true);
+ $negotiator->setSupportedSubProtocols([]);
+
+ $requestText = 'GET / HTTP/1.1
+Host: 127.0.0.1:6789
+Connection: Upgrade
+Pragma: no-cache
+Cache-Control: no-cache
+Upgrade: websocket
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Sec-WebSocket-Version: 13
+Sec-WebSocket-Protocol: someprotocol
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Accept-Encoding: gzip, deflate, sdch, br
+Accept-Language: en-US,en;q=0.8';
+
+ $request = \GuzzleHttp\Psr7\parse_request($requestText);
+
+ $response = $negotiator->handshake($request);
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ $this->assertEquals(426, $response->getStatusCode());
+ $this->assertEquals('No Sec-WebSocket-Protocols requested supported', $response->getReasonPhrase());
+ $this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
+ $this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
+ $this->assertEquals('13', $response->getHeaderLine('Sec-WebSocket-Version'));
+ }
+
+ public function testNonStrictSubprotocolDoesNotIncludeHeaderWhenNoneAgreedOn() {
+ $negotiator = new ServerNegotiator(new RequestVerifier());
+ $negotiator->setStrictSubProtocolCheck(false);
+ $negotiator->setSupportedSubProtocols(['someproto']);
+
+ $requestText = 'GET / HTTP/1.1
+Host: 127.0.0.1:6789
+Connection: Upgrade
+Pragma: no-cache
+Cache-Control: no-cache
+Upgrade: websocket
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Sec-WebSocket-Version: 13
+Sec-WebSocket-Protocol: someotherproto
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Accept-Encoding: gzip, deflate, sdch, br
+Accept-Language: en-US,en;q=0.8';
+
+ $request = \GuzzleHttp\Psr7\parse_request($requestText);
+
+ $response = $negotiator->handshake($request);
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ $this->assertEquals(101, $response->getStatusCode());
+ $this->assertEquals('Upgrade', $response->getHeaderLine('Connection'));
+ $this->assertEquals('websocket', $response->getHeaderLine('Upgrade'));
+ $this->assertFalse($response->hasHeader('Sec-WebSocket-Protocol'));
+ }
+}
\ No newline at end of file
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/FrameTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/FrameTest.php
new file mode 100644
index 0000000..b73f600
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/FrameTest.php
@@ -0,0 +1,501 @@
+_frame = new Frame;
+ }
+
+ /**
+ * Encode the fake binary string to send over the wire
+ * @param string of 1's and 0's
+ * @return string
+ */
+ public static function encode($in) {
+ if (strlen($in) > 8) {
+ $out = '';
+ while (strlen($in) >= 8) {
+ $out .= static::encode(substr($in, 0, 8));
+ $in = substr($in, 8);
+ }
+ return $out;
+ }
+ return chr(bindec($in));
+ }
+
+ /**
+ * This is a data provider
+ * param string The UTF8 message
+ * param string The WebSocket framed message, then base64_encoded
+ */
+ public static function UnframeMessageProvider() {
+ return array(
+ array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7'),
+ array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg'),
+ array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow=='),
+ array(
+ "The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...",
+ 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY='
+ )
+ );
+ }
+
+ public static function underflowProvider() {
+ return array(
+ array('isFinal', ''),
+ array('getRsv1', ''),
+ array('getRsv2', ''),
+ array('getRsv3', ''),
+ array('getOpcode', ''),
+ array('isMasked', '10000001'),
+ array('getPayloadLength', '10000001'),
+ array('getPayloadLength', '1000000111111110'),
+ array('getMaskingKey', '1000000110000111'),
+ array('getPayload', '100000011000000100011100101010101001100111110100')
+ );
+ }
+
+ /**
+ * @dataProvider underflowProvider
+ *
+ * @covers Ratchet\RFC6455\Messaging\Frame::isFinal
+ * @covers Ratchet\RFC6455\Messaging\Frame::getRsv1
+ * @covers Ratchet\RFC6455\Messaging\Frame::getRsv2
+ * @covers Ratchet\RFC6455\Messaging\Frame::getRsv3
+ * @covers Ratchet\RFC6455\Messaging\Frame::getOpcode
+ * @covers Ratchet\RFC6455\Messaging\Frame::isMasked
+ * @covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
+ * @covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
+ * @covers Ratchet\RFC6455\Messaging\Frame::getPayload
+ */
+ public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) {
+ $this->setExpectedException('\UnderflowException');
+ if (!empty($bin)) {
+ $this->_frame->addBuffer(static::encode($bin));
+ }
+ call_user_func(array($this->_frame, $method));
+ }
+
+ /**
+ * A data provider for testing the first byte of a WebSocket frame
+ * param bool Given, is the byte indicate this is the final frame
+ * param int Given, what is the expected opcode
+ * param string of 0|1 Each character represents a bit in the byte
+ */
+ public static function firstByteProvider() {
+ return array(
+ array(false, false, false, true, 8, '00011000'),
+ array(true, false, true, false, 10, '10101010'),
+ array(false, false, false, false, 15, '00001111'),
+ array(true, false, false, false, 1, '10000001'),
+ array(true, true, true, true, 15, '11111111'),
+ array(true, true, false, false, 7, '11000111')
+ );
+ }
+
+ /**
+ * @dataProvider firstByteProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::isFinal
+ */
+ public function testFinCodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
+ $this->_frame->addBuffer(static::encode($bin));
+ $this->assertEquals($fin, $this->_frame->isFinal());
+ }
+
+ /**
+ * @dataProvider firstByteProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getRsv1
+ * covers Ratchet\RFC6455\Messaging\Frame::getRsv2
+ * covers Ratchet\RFC6455\Messaging\Frame::getRsv3
+ */
+ public function testGetRsvFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
+ $this->_frame->addBuffer(static::encode($bin));
+ $this->assertEquals($rsv1, $this->_frame->getRsv1());
+ $this->assertEquals($rsv2, $this->_frame->getRsv2());
+ $this->assertEquals($rsv3, $this->_frame->getRsv3());
+ }
+
+ /**
+ * @dataProvider firstByteProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getOpcode
+ */
+ public function testOpcodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
+ $this->_frame->addBuffer(static::encode($bin));
+ $this->assertEquals($opcode, $this->_frame->getOpcode());
+ }
+
+ /**
+ * @dataProvider UnframeMessageProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::isFinal
+ */
+ public function testFinCodeFromFullMessage($msg, $encoded) {
+ $this->_frame->addBuffer(base64_decode($encoded));
+ $this->assertTrue($this->_frame->isFinal());
+ }
+
+ /**
+ * @dataProvider UnframeMessageProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getOpcode
+ */
+ public function testOpcodeFromFullMessage($msg, $encoded) {
+ $this->_frame->addBuffer(base64_decode($encoded));
+ $this->assertEquals(1, $this->_frame->getOpcode());
+ }
+
+ public static function payloadLengthDescriptionProvider() {
+ return array(
+ array(7, '01110101'),
+ array(7, '01111101'),
+ array(23, '01111110'),
+ array(71, '01111111'),
+ array(7, '00000000'), // Should this throw an exception? Can a payload be empty?
+ array(7, '00000001')
+ );
+ }
+
+ /**
+ * @dataProvider payloadLengthDescriptionProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::addBuffer
+ * covers Ratchet\RFC6455\Messaging\Frame::getFirstPayloadVal
+ */
+ public function testFirstPayloadDesignationValue($bits, $bin) {
+ $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
+ $this->_frame->addBuffer(static::encode($bin));
+ $ref = new \ReflectionClass($this->_frame);
+ $cb = $ref->getMethod('getFirstPayloadVal');
+ $cb->setAccessible(true);
+ $this->assertEquals(bindec($bin), $cb->invoke($this->_frame));
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::getFirstPayloadVal
+ */
+ public function testFirstPayloadValUnderflow() {
+ $ref = new \ReflectionClass($this->_frame);
+ $cb = $ref->getMethod('getFirstPayloadVal');
+ $cb->setAccessible(true);
+ $this->setExpectedException('UnderflowException');
+ $cb->invoke($this->_frame);
+ }
+
+ /**
+ * @dataProvider payloadLengthDescriptionProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getNumPayloadBits
+ */
+ public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) {
+ $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
+ $this->_frame->addBuffer(static::encode($bin));
+ $ref = new \ReflectionClass($this->_frame);
+ $cb = $ref->getMethod('getNumPayloadBits');
+ $cb->setAccessible(true);
+ $this->assertEquals($expected_bits, $cb->invoke($this->_frame));
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::getNumPayloadBits
+ */
+ public function testgetNumPayloadBitsUnderflow() {
+ $ref = new \ReflectionClass($this->_frame);
+ $cb = $ref->getMethod('getNumPayloadBits');
+ $cb->setAccessible(true);
+ $this->setExpectedException('UnderflowException');
+ $cb->invoke($this->_frame);
+ }
+
+ public function secondByteProvider() {
+ return array(
+ array(true, 1, '10000001'),
+ array(false, 1, '00000001'),
+ array(true, 125, $this->_secondByteMaskedSPL)
+ );
+ }
+ /**
+ * @dataProvider secondByteProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::isMasked
+ */
+ public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) {
+ $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
+ $this->_frame->addBuffer(static::encode($bin));
+ $this->assertEquals($masked, $this->_frame->isMasked());
+ }
+
+ /**
+ * @dataProvider UnframeMessageProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::isMasked
+ */
+ public function testIsMaskedFromFullMessage($msg, $encoded) {
+ $this->_frame->addBuffer(base64_decode($encoded));
+ $this->assertTrue($this->_frame->isMasked());
+ }
+
+ /**
+ * @dataProvider secondByteProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
+ */
+ public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) {
+ $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
+ $this->_frame->addBuffer(static::encode($bin));
+ $this->assertEquals($payload_length, $this->_frame->getPayloadLength());
+ }
+
+ /**
+ * @dataProvider UnframeMessageProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
+ * @todo Not yet testing when second additional payload length descriptor
+ */
+ public function testGetPayloadLengthFromFullMessage($msg, $encoded) {
+ $this->_frame->addBuffer(base64_decode($encoded));
+ $this->assertEquals(strlen($msg), $this->_frame->getPayloadLength());
+ }
+
+ public function maskingKeyProvider() {
+ $frame = new Frame;
+ return array(
+ array($frame->generateMaskingKey()),
+ array($frame->generateMaskingKey()),
+ array($frame->generateMaskingKey())
+ );
+ }
+
+ /**
+ * @dataProvider maskingKeyProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
+ * @todo I I wrote the dataProvider incorrectly, skipping for now
+ */
+ public function testGetMaskingKey($mask) {
+ $this->_frame->addBuffer(static::encode($this->_firstByteFinText));
+ $this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL));
+ $this->_frame->addBuffer($mask);
+ $this->assertEquals($mask, $this->_frame->getMaskingKey());
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::getMaskingKey
+ */
+ public function testGetMaskingKeyOnUnmaskedPayload() {
+ $frame = new Frame('Hello World!');
+ $this->assertEquals('', $frame->getMaskingKey());
+ }
+
+ /**
+ * @dataProvider UnframeMessageProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getPayload
+ * @todo Move this test to bottom as it requires all methods of the class
+ */
+ public function testUnframeFullMessage($unframed, $base_framed) {
+ $this->_frame->addBuffer(base64_decode($base_framed));
+ $this->assertEquals($unframed, $this->_frame->getPayload());
+ }
+
+ public static function messageFragmentProvider() {
+ return array(
+ array(false, '', '', '', '', '')
+ );
+ }
+
+ /**
+ * @dataProvider UnframeMessageProvider
+ * covers Ratchet\RFC6455\Messaging\Frame::getPayload
+ */
+ public function testCheckPiecingTogetherMessage($msg, $encoded) {
+ $framed = base64_decode($encoded);
+ for ($i = 0, $len = strlen($framed);$i < $len; $i++) {
+ $this->_frame->addBuffer(substr($framed, $i, 1));
+ }
+ $this->assertEquals($msg, $this->_frame->getPayload());
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::__construct
+ * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
+ * covers Ratchet\RFC6455\Messaging\Frame::getPayload
+ */
+ public function testLongCreate() {
+ $len = 65525;
+ $pl = $this->generateRandomString($len);
+ $frame = new Frame($pl, true, Frame::OP_PING);
+ $this->assertTrue($frame->isFinal());
+ $this->assertEquals(Frame::OP_PING, $frame->getOpcode());
+ $this->assertFalse($frame->isMasked());
+ $this->assertEquals($len, $frame->getPayloadLength());
+ $this->assertEquals($pl, $frame->getPayload());
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::__construct
+ * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
+ */
+ public function testReallyLongCreate() {
+ $len = 65575;
+ $frame = new Frame($this->generateRandomString($len));
+ $this->assertEquals($len, $frame->getPayloadLength());
+ }
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::__construct
+ * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
+ */
+ public function testExtractOverflow() {
+ $string1 = $this->generateRandomString();
+ $frame1 = new Frame($string1);
+ $string2 = $this->generateRandomString();
+ $frame2 = new Frame($string2);
+ $cat = new Frame;
+ $cat->addBuffer($frame1->getContents() . $frame2->getContents());
+ $this->assertEquals($frame1->getContents(), $cat->getContents());
+ $this->assertEquals($string1, $cat->getPayload());
+ $uncat = new Frame;
+ $uncat->addBuffer($cat->extractOverflow());
+ $this->assertEquals($string1, $cat->getPayload());
+ $this->assertEquals($string2, $uncat->getPayload());
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
+ */
+ public function testEmptyExtractOverflow() {
+ $string = $this->generateRandomString();
+ $frame = new Frame($string);
+ $this->assertEquals($string, $frame->getPayload());
+ $this->assertEquals('', $frame->extractOverflow());
+ $this->assertEquals($string, $frame->getPayload());
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::getContents
+ */
+ public function testGetContents() {
+ $msg = 'The quick brown fox jumps over the lazy dog.';
+ $frame1 = new Frame($msg);
+ $frame2 = new Frame($msg);
+ $frame2->maskPayload();
+ $this->assertNotEquals($frame1->getContents(), $frame2->getContents());
+ $this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents()));
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
+ */
+ public function testMasking() {
+ $msg = 'The quick brown fox jumps over the lazy dog.';
+ $frame = new Frame($msg);
+ $frame->maskPayload();
+ $this->assertTrue($frame->isMasked());
+ $this->assertEquals($msg, $frame->getPayload());
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::unMaskPayload
+ */
+ public function testUnMaskPayload() {
+ $string = $this->generateRandomString();
+ $frame = new Frame($string);
+ $frame->maskPayload()->unMaskPayload();
+ $this->assertFalse($frame->isMasked());
+ $this->assertEquals($string, $frame->getPayload());
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::generateMaskingKey
+ */
+ public function testGenerateMaskingKey() {
+ $dupe = false;
+ $done = array();
+ for ($i = 0; $i < 10; $i++) {
+ $new = $this->_frame->generateMaskingKey();
+ if (in_array($new, $done)) {
+ $dupe = true;
+ }
+ $done[] = $new;
+ }
+ $this->assertEquals(4, strlen($new));
+ $this->assertFalse($dupe);
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
+ */
+ public function testGivenMaskIsValid() {
+ $this->setExpectedException('InvalidArgumentException');
+ $this->_frame->maskPayload('hello world');
+ }
+
+ /**
+ * covers Ratchet\RFC6455\Messaging\Frame::maskPayload
+ */
+ public function testGivenMaskIsValidAscii() {
+ if (!extension_loaded('mbstring')) {
+ $this->markTestSkipped("mbstring required for this test");
+ return;
+ }
+ $this->setExpectedException('OutOfBoundsException');
+ $this->_frame->maskPayload('x✖');
+ }
+
+ protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) {
+ $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง
+ $useChars = array();
+ for($i = 0; $i < $length; $i++) {
+ $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)];
+ }
+ if($addSpaces === true) {
+ array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' ');
+ }
+ if($addNumbers === true) {
+ array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9));
+ }
+ shuffle($useChars);
+ $randomString = trim(implode('', $useChars));
+ $randomString = substr($randomString, 0, $length);
+ return $randomString;
+ }
+
+ /**
+ * There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than
+ * 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame
+ * to set the payload length to 126 and then not recalculate it once the full length information was available.
+ *
+ * This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown.
+ *
+ * covers Ratchet\RFC6455\Messaging\Frame::getPayloadLength
+ * covers Ratchet\RFC6455\Messaging\Frame::extractOverflow
+ */
+ public function testFrameDeliveredOneByteAtATime() {
+ $startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final
+ $framePayload = str_repeat("*", 256);
+ $rawOverflow = "xyz";
+ $rawFrame = $startHeader . $framePayload . $rawOverflow;
+ $frame = new Frame();
+ $payloadLen = 256;
+ for ($i = 0; $i < strlen($rawFrame); $i++) {
+ $frame->addBuffer($rawFrame[$i]);
+ try {
+ // payloadLen will
+ $payloadLen = $frame->getPayloadLength();
+ } catch (\UnderflowException $e) {
+ if ($i > 2) { // we should get an underflow on 0,1,2
+ $this->fail("Underflow exception when the frame length should be available");
+ }
+ }
+ if ($payloadLen !== 256) {
+ $this->fail("Payload length of " . $payloadLen . " should have been 256.");
+ }
+ }
+ // make sure the overflow is good
+ $this->assertEquals($rawOverflow, $frame->extractOverflow());
+ }
+}
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageBufferTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageBufferTest.php
new file mode 100644
index 0000000..c33ff0c
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageBufferTest.php
@@ -0,0 +1,39 @@
+getContents();
+
+ $data = str_repeat($frameRaw, 1000);
+
+ $messageCount = 0;
+
+ $messageBuffer = new MessageBuffer(
+ new CloseFrameChecker(),
+ function (Message $message) use (&$messageCount) {
+ $messageCount++;
+ $this->assertEquals('a', $message->getPayload());
+ },
+ null,
+ false
+ );
+
+ $messageBuffer->onData($data);
+
+ $this->assertEquals(1000, $messageCount);
+ }
+}
\ No newline at end of file
diff --git a/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageTest.php b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageTest.php
new file mode 100644
index 0000000..1f7eab5
--- /dev/null
+++ b/assets/php/vendor/ratchet/rfc6455/tests/unit/Messaging/MessageTest.php
@@ -0,0 +1,58 @@
+message = new Message;
+ }
+
+ public function testNoFrames() {
+ $this->assertFalse($this->message->isCoalesced());
+ }
+
+ public function testNoFramesOpCode() {
+ $this->setExpectedException('UnderflowException');
+ $this->message->getOpCode();
+ }
+
+ public function testFragmentationPayload() {
+ $a = 'Hello ';
+ $b = 'World!';
+ $f1 = new Frame($a, false);
+ $f2 = new Frame($b, true, Frame::OP_CONTINUE);
+ $this->message->addFrame($f1)->addFrame($f2);
+ $this->assertEquals(strlen($a . $b), $this->message->getPayloadLength());
+ $this->assertEquals($a . $b, $this->message->getPayload());
+ }
+
+ public function testUnbufferedFragment() {
+ $this->message->addFrame(new Frame('The quick brow', false));
+ $this->setExpectedException('UnderflowException');
+ $this->message->getPayload();
+ }
+
+ public function testGetOpCode() {
+ $this->message
+ ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT))
+ ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE))
+ ->addFrame(new Frame('er the lazy dog', true, Frame::OP_CONTINUE))
+ ;
+ $this->assertEquals(Frame::OP_TEXT, $this->message->getOpCode());
+ }
+
+ public function testGetUnBufferedPayloadLength() {
+ $this->message
+ ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT))
+ ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE))
+ ;
+ $this->assertEquals(28, $this->message->getPayloadLength());
+ }
+}
\ No newline at end of file
diff --git a/assets/php/vendor/react/cache/.gitignore b/assets/php/vendor/react/cache/.gitignore
new file mode 100644
index 0000000..987e2a2
--- /dev/null
+++ b/assets/php/vendor/react/cache/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/assets/php/vendor/react/cache/.travis.yml b/assets/php/vendor/react/cache/.travis.yml
new file mode 100644
index 0000000..290df75
--- /dev/null
+++ b/assets/php/vendor/react/cache/.travis.yml
@@ -0,0 +1,25 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/assets/php/vendor/react/cache/CHANGELOG.md b/assets/php/vendor/react/cache/CHANGELOG.md
new file mode 100644
index 0000000..19d1801
--- /dev/null
+++ b/assets/php/vendor/react/cache/CHANGELOG.md
@@ -0,0 +1,35 @@
+# Changelog
+
+## 0.4.2 (2017-12-20)
+
+* Improve documentation with usage and installation instructions
+ (#10 by @clue)
+
+* Improve test suite by adding PHPUnit to `require-dev` and
+ add forward compatibility with PHPUnit 5 and PHPUnit 6 and
+ sanitize Composer autoload paths
+ (#14 by @shaunbramley and #12 and #18 by @clue)
+
+## 0.4.1 (2016-02-25)
+
+* Repository maintenance, split off from main repo, improve test suite and documentation
+* First class support for PHP7 and HHVM (#9 by @clue)
+* Adjust compatibility to 5.3 (#7 by @clue)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.2 (2013-05-10)
+
+* Version bump
+
+## 0.3.0 (2013-04-14)
+
+* Version bump
+
+## 0.2.6 (2012-12-26)
+
+* Feature: New cache component, used by DNS
diff --git a/assets/php/vendor/react/cache/LICENSE b/assets/php/vendor/react/cache/LICENSE
new file mode 100644
index 0000000..a808108
--- /dev/null
+++ b/assets/php/vendor/react/cache/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+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.
diff --git a/assets/php/vendor/react/cache/README.md b/assets/php/vendor/react/cache/README.md
new file mode 100644
index 0000000..70ad40a
--- /dev/null
+++ b/assets/php/vendor/react/cache/README.md
@@ -0,0 +1,171 @@
+# Cache Component
+
+[](http://travis-ci.org/reactphp/cache) [](https://codeclimate.com/github/reactphp/cache)
+
+Async, [Promise](https://github.com/reactphp/promise)-based cache interface
+for [ReactPHP](https://reactphp.org/).
+
+The cache component provides a
+[Promise](https://github.com/reactphp/promise)-based
+[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache)
+implementation of that.
+This allows consumers to type hint against the interface and third parties to
+provide alternate implementations.
+
+**Table of Contents**
+
+* [Usage](#usage)
+ * [CacheInterface](#cacheinterface)
+ * [get()](#get)
+ * [set()](#set)
+ * [remove()](#remove)
+ * [ArrayCache](#arraycache)
+* [Common usage](#common-usage)
+ * [Fallback get](#fallback-get)
+ * [Fallback-get-and-set](#fallback-get-and-set)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Usage
+
+### CacheInterface
+
+The `CacheInterface` describes the main interface of this component.
+This allows consumers to type hint against the interface and third parties to
+provide alternate implementations.
+
+#### get()
+
+```php
+$cache
+ ->get('foo')
+ ->then('var_dump');
+```
+
+This example fetches the value of the key `foo` and passes it to the
+`var_dump` function. You can use any of the composition provided by
+[promises](https://github.com/reactphp/promise).
+
+If the key `foo` does not exist, the promise will be rejected.
+
+#### set()
+
+```php
+$cache->set('foo', 'bar');
+```
+
+This example eventually sets the value of the key `foo` to `bar`. If it
+already exists, it is overridden. No guarantees are made as to when the cache
+value is set. If the cache implementation has to go over the network to store
+it, it may take a while.
+
+#### remove()
+
+```php
+$cache->remove('foo');
+```
+
+This example eventually removes the key `foo` from the cache. As with `set`,
+this may not happen instantly.
+
+### ArrayCache
+
+The `ArrayCache` provides an in-memory implementation of the
+[`CacheInterface`](#cacheinterface).
+
+```php
+$cache = new ArrayCache();
+
+$cache->set('foo', 'bar');
+```
+
+## Common usage
+
+### Fallback get
+
+A common use case of caches is to attempt fetching a cached value and as a
+fallback retrieve it from the original data source if not found. Here is an
+example of that:
+
+```php
+$cache
+ ->get('foo')
+ ->then(null, 'getFooFromDb')
+ ->then('var_dump');
+```
+
+First an attempt is made to retrieve the value of `foo`. A promise rejection
+handler of the function `getFooFromDb` is registered. `getFooFromDb` is a
+function (can be any PHP callable) that will be called if the key does not
+exist in the cache.
+
+`getFooFromDb` can handle the missing key by returning a promise for the
+actual value from the database (or any other data source). As a result, this
+chain will correctly fall back, and provide the value in both cases.
+
+### Fallback get and set
+
+To expand on the fallback get example, often you want to set the value on the
+cache after fetching it from the data source.
+
+```php
+$cache
+ ->get('foo')
+ ->then(null, array($this, 'getAndCacheFooFromDb'))
+ ->then('var_dump');
+
+public function getAndCacheFooFromDb()
+{
+ return $this->db
+ ->get('foo')
+ ->then(array($this, 'cacheFooFromDb'));
+}
+
+public function cacheFooFromDb($foo)
+{
+ $this->cache->set('foo', $foo);
+
+ return $foo;
+}
+```
+
+By using chaining you can easily conditionally cache the value if it is
+fetched from the database.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/cache:^0.4.2
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/assets/php/vendor/react/cache/composer.json b/assets/php/vendor/react/cache/composer.json
new file mode 100644
index 0000000..51573b6
--- /dev/null
+++ b/assets/php/vendor/react/cache/composer.json
@@ -0,0 +1,19 @@
+{
+ "name": "react/cache",
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": ["cache", "caching", "promise", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "~2.0|~1.1"
+ },
+ "autoload": {
+ "psr-4": { "React\\Cache\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Cache\\": "tests/" }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/assets/php/vendor/react/cache/phpunit.xml.dist b/assets/php/vendor/react/cache/phpunit.xml.dist
new file mode 100644
index 0000000..d02182f
--- /dev/null
+++ b/assets/php/vendor/react/cache/phpunit.xml.dist
@@ -0,0 +1,20 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/assets/php/vendor/react/cache/src/ArrayCache.php b/assets/php/vendor/react/cache/src/ArrayCache.php
new file mode 100644
index 0000000..03dcc15
--- /dev/null
+++ b/assets/php/vendor/react/cache/src/ArrayCache.php
@@ -0,0 +1,29 @@
+data[$key])) {
+ return Promise\reject();
+ }
+
+ return Promise\resolve($this->data[$key]);
+ }
+
+ public function set($key, $value)
+ {
+ $this->data[$key] = $value;
+ }
+
+ public function remove($key)
+ {
+ unset($this->data[$key]);
+ }
+}
diff --git a/assets/php/vendor/react/cache/src/CacheInterface.php b/assets/php/vendor/react/cache/src/CacheInterface.php
new file mode 100644
index 0000000..fd5f2d5
--- /dev/null
+++ b/assets/php/vendor/react/cache/src/CacheInterface.php
@@ -0,0 +1,13 @@
+cache = new ArrayCache();
+ }
+
+ /** @test */
+ public function getShouldRejectPromiseForNonExistentKey()
+ {
+ $this->cache
+ ->get('foo')
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function setShouldSetKey()
+ {
+ $this->cache
+ ->set('foo', 'bar');
+
+ $success = $this->createCallableMock();
+ $success
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with('bar');
+
+ $this->cache
+ ->get('foo')
+ ->then($success);
+ }
+
+ /** @test */
+ public function removeShouldRemoveKey()
+ {
+ $this->cache
+ ->set('foo', 'bar');
+
+ $this->cache
+ ->remove('foo');
+
+ $this->cache
+ ->get('foo')
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+}
diff --git a/assets/php/vendor/react/cache/tests/CallableStub.php b/assets/php/vendor/react/cache/tests/CallableStub.php
new file mode 100644
index 0000000..2f547cd
--- /dev/null
+++ b/assets/php/vendor/react/cache/tests/CallableStub.php
@@ -0,0 +1,10 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Cache\CallableStub')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/dns/.gitignore b/assets/php/vendor/react/dns/.gitignore
new file mode 100644
index 0000000..19982ea
--- /dev/null
+++ b/assets/php/vendor/react/dns/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
\ No newline at end of file
diff --git a/assets/php/vendor/react/dns/.travis.yml b/assets/php/vendor/react/dns/.travis.yml
new file mode 100644
index 0000000..41921e3
--- /dev/null
+++ b/assets/php/vendor/react/dns/.travis.yml
@@ -0,0 +1,29 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/assets/php/vendor/react/dns/CHANGELOG.md b/assets/php/vendor/react/dns/CHANGELOG.md
new file mode 100644
index 0000000..adad0a7
--- /dev/null
+++ b/assets/php/vendor/react/dns/CHANGELOG.md
@@ -0,0 +1,179 @@
+# Changelog
+
+## 0.4.13 (2018-02-27)
+
+* Add `Config::loadSystemConfigBlocking()` to load default system config
+ and support parsing DNS config on all supported platforms
+ (`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
+ (#92, #93, #94 and #95 by @clue)
+
+ ```php
+ $config = Config::loadSystemConfigBlocking();
+ $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+ ```
+
+* Remove unneeded cyclic dependency on react/socket
+ (#96 by @clue)
+
+## 0.4.12 (2018-01-14)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6,
+ test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
+ add test group to skip integration tests relying on internet connection
+ and add minor documentation improvements.
+ (#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
+
+## 0.4.11 (2017-08-25)
+
+* Feature: Support resolving from default hosts file
+ (#75, #76 and #77 by @clue)
+
+ This means that resolving hosts such as `localhost` will now work as
+ expected across all platforms with no changes required:
+
+ ```php
+ $resolver->resolve('localhost')->then(function ($ip) {
+ echo 'IP: ' . $ip;
+ });
+ ```
+
+ The new `HostsExecutor` exists for advanced usage and is otherwise used
+ internally for this feature.
+
+## 0.4.10 (2017-08-10)
+
+* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
+ lock minimum dependencies and work around circular dependency for tests
+ (#70 and #71 by @clue)
+
+* Fix: Work around DNS timeout issues for Windows users
+ (#74 by @clue)
+
+* Documentation and examples for advanced usage
+ (#66 by @WyriHaximus)
+
+* Remove broken TCP code, do not retry with invalid TCP query
+ (#73 by @clue)
+
+* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
+ lock Travis distro so new defaults will not break the build and
+ fix failing tests for PHP 7.1
+ (#68 by @WyriHaximus and #69 and #72 by @clue)
+
+## 0.4.9 (2017-05-01)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#61 by @clue)
+
+## 0.4.8 (2017-04-16)
+
+* Feature: Add support for the AAAA record type to the protocol parser
+ (#58 by @othillo)
+
+* Feature: Add support for the PTR record type to the protocol parser
+ (#59 by @othillo)
+
+## 0.4.7 (2017-03-31)
+
+* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
+ (#57 by @clue)
+
+## 0.4.6 (2017-03-11)
+
+* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
+ with Stream v0.5 and upcoming v0.6
+ (#53 by @clue)
+
+* Improve test suite by adding PHPUnit to `require-dev`
+ (#54 by @clue)
+
+## 0.4.5 (2017-03-02)
+
+* Fix: Ensure we ignore the case of the answer
+ (#51 by @WyriHaximus)
+
+* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
+ code re-use for upcoming versions.
+ (#48 and #49 by @clue)
+
+## 0.4.4 (2017-02-13)
+
+* Fix: Fix handling connection and stream errors
+ (#45 by @clue)
+
+* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
+ (#46 and #47 by @clue)
+
+## 0.4.3 (2016-07-31)
+
+* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
+
+ ```php
+ $factory = new React\Dns\Resolver\Factory();
+
+ $cache = new MyCustomCacheInstance();
+ $resolver = $factory->createCached('8.8.8.8', $loop, $cache);
+ ```
+
+* Feature: Support Promise cancellation (#35 by @clue)
+
+ ```php
+ $promise = $resolver->resolve('reactphp.org');
+
+ $promise->cancel();
+ ```
+
+## 0.4.2 (2016-02-24)
+
+* Repository maintenance, split off from main repo, improve test suite and documentation
+* First class support for PHP7 and HHVM (#34 by @clue)
+* Adjust compatibility to 5.3 (#30 by @clue)
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* Bug fix: Properly resolve CNAME aliases
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+* Bump React dependencies to v0.4
+
+## 0.3.2 (2013-05-10)
+
+* Feature: Support default port for IPv6 addresses (@clue)
+
+## 0.3.0 (2013-04-14)
+
+* Bump React dependencies to v0.3
+
+## 0.2.6 (2012-12-26)
+
+* Feature: New cache component, used by DNS
+
+## 0.2.5 (2012-11-26)
+
+* Version bump
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Change to promise-based API (@jsor)
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Feature: DNS executor timeout handling (@arnaud-lb)
+* Feature: DNS retry executor (@arnaud-lb)
+
+## 0.2.1 (2012-10-14)
+
+* Minor adjustments to DNS parser
+
+## 0.2.0 (2012-09-10)
+
+* Feature: DNS resolver
diff --git a/assets/php/vendor/react/dns/LICENSE b/assets/php/vendor/react/dns/LICENSE
new file mode 100644
index 0000000..a808108
--- /dev/null
+++ b/assets/php/vendor/react/dns/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+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.
diff --git a/assets/php/vendor/react/dns/README.md b/assets/php/vendor/react/dns/README.md
new file mode 100644
index 0000000..ed86667
--- /dev/null
+++ b/assets/php/vendor/react/dns/README.md
@@ -0,0 +1,209 @@
+# Dns
+
+[](https://travis-ci.org/reactphp/dns)
+
+Async DNS resolver for [ReactPHP](https://reactphp.org/).
+
+The main point of the DNS component is to provide async DNS resolution.
+However, it is really a toolkit for working with DNS messages, and could
+easily be used to create a DNS server.
+
+**Table of contents**
+
+* [Basic usage](#basic-usage)
+* [Caching](#caching)
+ * [Custom cache adapter](#custom-cache-adapter)
+* [Advanced usage](#advanced-usage)
+ * [HostsFileExecutor](#hostsfileexecutor)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [References](#references)
+
+## Basic usage
+
+The most basic usage is to just create a resolver through the resolver
+factory. All you need to give it is a nameserver, then you can start resolving
+names, baby!
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$config = React\Dns\Config\Config::loadSystemConfigBlocking();
+$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->create($server, $loop);
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+$loop->run();
+```
+
+See also the [first example](examples).
+
+The `Config` class can be used to load the system default config. This is an
+operation that may access the filesystem and block. Ideally, this method should
+thus be executed only once before the loop starts and not repeatedly while it is
+running.
+Note that this class may return an *empty* configuration if the system config
+can not be loaded. As such, you'll likely want to apply a default nameserver
+as above if none can be found.
+
+> Note that the factory loads the hosts file from the filesystem once when
+ creating the resolver instance.
+ Ideally, this method should thus be executed only once before the loop starts
+ and not repeatedly while it is running.
+
+Pending DNS queries can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $resolver->resolve('reactphp.org');
+
+$promise->cancel();
+```
+
+But there's more.
+
+## Caching
+
+You can cache results by configuring the resolver to use a `CachedExecutor`:
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$config = React\Dns\Config\Config::loadSystemConfigBlocking();
+$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->createCached($server, $loop);
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+...
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+$loop->run();
+```
+
+If the first call returns before the second, only one query will be executed.
+The second result will be served from an in memory cache.
+This is particularly useful for long running scripts where the same hostnames
+have to be looked up multiple times.
+
+See also the [third example](examples).
+
+### Custom cache adapter
+
+By default, the above will use an in memory cache.
+
+You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
+
+```php
+$cache = new React\Cache\ArrayCache();
+$loop = React\EventLoop\Factory::create();
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->createCached('8.8.8.8', $loop, $cache);
+```
+
+See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
+
+## Advanced Usage
+
+For more advanced usages one can utilize the `React\Dns\Query\Executor` directly.
+The following example looks up the `IPv6` address for `igor.io`.
+
+```php
+$loop = Factory::create();
+
+$executor = new Executor($loop, new Parser(), new BinaryDumper(), null);
+
+$executor->query(
+ '8.8.8.8:53',
+ new Query($name, Message::TYPE_AAAA, Message::CLASS_IN, time())
+)->done(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv6: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
+
+```
+
+See also the [fourth example](examples).
+
+### HostsFileExecutor
+
+Note that the above `Executor` class always performs an actual DNS query.
+If you also want to take entries from your hosts file into account, you may
+use this code:
+
+```php
+$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
+
+$executor = new Executor($loop, new Parser(), new BinaryDumper(), null);
+$executor = new HostsFileExecutor($hosts, $executor);
+
+$executor->query(
+ '8.8.8.8:53',
+ new Query('localhost', Message::TYPE_A, Message::CLASS_IN, time())
+);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/dns:^0.4.13
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## References
+
+* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
+* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification
diff --git a/assets/php/vendor/react/dns/composer.json b/assets/php/vendor/react/dns/composer.json
new file mode 100644
index 0000000..510a43c
--- /dev/null
+++ b/assets/php/vendor/react/dns/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "react/dns",
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "~0.4.0|~0.3.0",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.1 || ^1.2.1",
+ "react/promise-timer": "^1.2",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": { "React\\Dns\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Dns\\": "tests" }
+ }
+}
diff --git a/assets/php/vendor/react/dns/examples/01-one.php b/assets/php/vendor/react/dns/examples/01-one.php
new file mode 100644
index 0000000..5db164f
--- /dev/null
+++ b/assets/php/vendor/react/dns/examples/01-one.php
@@ -0,0 +1,22 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+}, 'printf');
+
+$loop->run();
diff --git a/assets/php/vendor/react/dns/examples/02-concurrent.php b/assets/php/vendor/react/dns/examples/02-concurrent.php
new file mode 100644
index 0000000..87e3f5c
--- /dev/null
+++ b/assets/php/vendor/react/dns/examples/02-concurrent.php
@@ -0,0 +1,27 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$names = array_slice($argv, 1);
+if (!$names) {
+ $names = array('google.com', 'www.google.com', 'gmail.com');
+}
+
+foreach ($names as $name) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+}
+
+$loop->run();
diff --git a/assets/php/vendor/react/dns/examples/03-cached.php b/assets/php/vendor/react/dns/examples/03-cached.php
new file mode 100644
index 0000000..e76a27c
--- /dev/null
+++ b/assets/php/vendor/react/dns/examples/03-cached.php
@@ -0,0 +1,40 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->createCached($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+}, 'printf');
+
+$loop->addTimer(1.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->addTimer(2.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->addTimer(3.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php b/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php
new file mode 100644
index 0000000..6c46bbf
--- /dev/null
+++ b/assets/php/vendor/react/dns/examples/04-query-a-and-aaaa.php
@@ -0,0 +1,32 @@
+query('8.8.8.8:53', $ipv4Query)->done(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv4: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+$executor->query('8.8.8.8:53', $ipv6Query)->done(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv6: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
diff --git a/assets/php/vendor/react/dns/phpunit.xml.dist b/assets/php/vendor/react/dns/phpunit.xml.dist
new file mode 100644
index 0000000..13d3fab
--- /dev/null
+++ b/assets/php/vendor/react/dns/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/assets/php/vendor/react/dns/src/BadServerException.php b/assets/php/vendor/react/dns/src/BadServerException.php
new file mode 100644
index 0000000..3bf50f1
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/BadServerException.php
@@ -0,0 +1,7 @@
+nameservers = $matches[1];
+
+ return $config;
+ }
+
+ /**
+ * Loads the DNS configurations from Windows's WMIC (from the given command or default command)
+ *
+ * Note that this method blocks while loading the given command and should
+ * thus be used with care! While this should be relatively fast for normal
+ * WMIC commands, it remains unknown if this may block under certain
+ * circumstances. In particular, this method should only be executed before
+ * the loop starts, not while it is running.
+ *
+ * Note that this method will only try to execute the given command try to
+ * parse its output, irrespective of whether this command exists. In
+ * particular, this command is only available on Windows. Currently, this
+ * will only parse valid nameserver entries from the command output and will
+ * ignore all other output without complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
+ * @return self
+ * @link https://ss64.com/nt/wmic.html
+ */
+ public static function loadWmicBlocking($command = null)
+ {
+ $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
+ preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
+
+ $config = new self();
+ $config->nameservers = $matches[1];
+
+ return $config;
+ }
+
+ public $nameservers = array();
+}
diff --git a/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php b/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php
new file mode 100644
index 0000000..68cec3e
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Config/FilesystemFactory.php
@@ -0,0 +1,73 @@
+loop = $loop;
+ }
+
+ public function create($filename)
+ {
+ return $this
+ ->loadEtcResolvConf($filename)
+ ->then(array($this, 'parseEtcResolvConf'));
+ }
+
+ /**
+ * @param string $contents
+ * @return Promise
+ * @deprecated see Config instead
+ */
+ public function parseEtcResolvConf($contents)
+ {
+ return Promise\resolve(Config::loadResolvConfBlocking(
+ 'data://text/plain;base64,' . base64_encode($contents)
+ ));
+ }
+
+ public function loadEtcResolvConf($filename)
+ {
+ if (!file_exists($filename)) {
+ return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename"));
+ }
+
+ try {
+ $deferred = new Deferred();
+
+ $fd = fopen($filename, 'r');
+ stream_set_blocking($fd, 0);
+
+ $contents = '';
+
+ $stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop);
+ $stream->on('data', function ($data) use (&$contents) {
+ $contents .= $data;
+ });
+ $stream->on('end', function () use (&$contents, $deferred) {
+ $deferred->resolve($contents);
+ });
+ $stream->on('error', function ($error) use ($deferred) {
+ $deferred->reject($error);
+ });
+
+ return $deferred->promise();
+ } catch (\Exception $e) {
+ return Promise\reject($e);
+ }
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Config/HostsFile.php b/assets/php/vendor/react/dns/src/Config/HostsFile.php
new file mode 100644
index 0000000..5b6277e
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Config/HostsFile.php
@@ -0,0 +1,151 @@
+contents = $contents;
+ }
+
+ /**
+ * Returns all IPs for the given hostname
+ *
+ * @param string $name
+ * @return string[]
+ */
+ public function getIpsForHost($name)
+ {
+ $name = strtolower($name);
+
+ $ips = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line);
+ $ip = array_shift($parts);
+ if ($parts && array_search($name, $parts) !== false) {
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
+ $ip = substr($ip, 0, $pos);
+ }
+
+ if (@inet_pton($ip) !== false) {
+ $ips[] = $ip;
+ }
+ }
+ }
+
+ return $ips;
+ }
+
+ /**
+ * Returns all hostnames for the given IPv4 or IPv6 address
+ *
+ * @param string $ip
+ * @return string[]
+ */
+ public function getHostsForIp($ip)
+ {
+ // check binary representation of IP to avoid string case and short notation
+ $ip = @inet_pton($ip);
+ if ($ip === false) {
+ return array();
+ }
+
+ $names = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line, null, PREG_SPLIT_NO_EMPTY);
+ $addr = array_shift($parts);
+
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
+ $addr = substr($addr, 0, $pos);
+ }
+
+ if (@inet_pton($addr) === $ip) {
+ foreach ($parts as $part) {
+ $names[] = $part;
+ }
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Model/HeaderBag.php b/assets/php/vendor/react/dns/src/Model/HeaderBag.php
new file mode 100644
index 0000000..193e65c
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Model/HeaderBag.php
@@ -0,0 +1,56 @@
+ 0,
+ 'anCount' => 0,
+ 'nsCount' => 0,
+ 'arCount' => 0,
+ 'qr' => 0,
+ 'opcode' => Message::OPCODE_QUERY,
+ 'aa' => 0,
+ 'tc' => 0,
+ 'rd' => 0,
+ 'ra' => 0,
+ 'z' => 0,
+ 'rcode' => Message::RCODE_OK,
+ );
+
+ public function get($name)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
+ }
+
+ public function set($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ public function isQuery()
+ {
+ return 0 === $this->attributes['qr'];
+ }
+
+ public function isResponse()
+ {
+ return 1 === $this->attributes['qr'];
+ }
+
+ public function isTruncated()
+ {
+ return 1 === $this->attributes['tc'];
+ }
+
+ public function populateCounts(Message $message)
+ {
+ $this->attributes['qdCount'] = count($message->questions);
+ $this->attributes['anCount'] = count($message->answers);
+ $this->attributes['nsCount'] = count($message->authority);
+ $this->attributes['arCount'] = count($message->additional);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Model/Message.php b/assets/php/vendor/react/dns/src/Model/Message.php
new file mode 100644
index 0000000..715cb1f
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Model/Message.php
@@ -0,0 +1,100 @@
+header->set('id', self::generateId());
+ $request->header->set('rd', 1);
+ $request->questions[] = (array) $query;
+ $request->prepare();
+
+ return $request;
+ }
+
+ /**
+ * Creates a new response message for the given query with the given answer records
+ *
+ * @param Query $query
+ * @param Record[] $answers
+ * @return self
+ */
+ public static function createResponseWithAnswersForQuery(Query $query, array $answers)
+ {
+ $response = new Message();
+ $response->header->set('id', self::generateId());
+ $response->header->set('qr', 1);
+ $response->header->set('opcode', Message::OPCODE_QUERY);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = (array) $query;
+
+ foreach ($answers as $record) {
+ $response->answers[] = $record;
+ }
+
+ $response->prepare();
+
+ return $response;
+ }
+
+ private static function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+
+ public $data = '';
+
+ public $header;
+ public $questions = array();
+ public $answers = array();
+ public $authority = array();
+ public $additional = array();
+
+ public $consumed = 0;
+
+ public function __construct()
+ {
+ $this->header = new HeaderBag();
+ }
+
+ public function prepare()
+ {
+ $this->header->populateCounts($this);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Model/Record.php b/assets/php/vendor/react/dns/src/Model/Record.php
new file mode 100644
index 0000000..029d232
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Model/Record.php
@@ -0,0 +1,21 @@
+name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->ttl = $ttl;
+ $this->data = $data;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php b/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php
new file mode 100644
index 0000000..35d6ae6
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Protocol/BinaryDumper.php
@@ -0,0 +1,62 @@
+headerToBinary($message->header);
+ $data .= $this->questionToBinary($message->questions);
+
+ return $data;
+ }
+
+ private function headerToBinary(HeaderBag $header)
+ {
+ $data = '';
+
+ $data .= pack('n', $header->get('id'));
+
+ $flags = 0x00;
+ $flags = ($flags << 1) | $header->get('qr');
+ $flags = ($flags << 4) | $header->get('opcode');
+ $flags = ($flags << 1) | $header->get('aa');
+ $flags = ($flags << 1) | $header->get('tc');
+ $flags = ($flags << 1) | $header->get('rd');
+ $flags = ($flags << 1) | $header->get('ra');
+ $flags = ($flags << 3) | $header->get('z');
+ $flags = ($flags << 4) | $header->get('rcode');
+
+ $data .= pack('n', $flags);
+
+ $data .= pack('n', $header->get('qdCount'));
+ $data .= pack('n', $header->get('anCount'));
+ $data .= pack('n', $header->get('nsCount'));
+ $data .= pack('n', $header->get('arCount'));
+
+ return $data;
+ }
+
+ private function questionToBinary(array $questions)
+ {
+ $data = '';
+
+ foreach ($questions as $question) {
+ $labels = explode('.', $question['name']);
+ foreach ($labels as $label) {
+ $data .= chr(strlen($label)).$label;
+ }
+ $data .= "\x00";
+
+ $data .= pack('n*', $question['type'], $question['class']);
+ }
+
+ return $data;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Protocol/Parser.php b/assets/php/vendor/react/dns/src/Protocol/Parser.php
new file mode 100644
index 0000000..1191cd3
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Protocol/Parser.php
@@ -0,0 +1,254 @@
+parse($data, $message) !== $message) {
+ throw new InvalidArgumentException('Unable to parse binary message');
+ }
+
+ return $message;
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function parseChunk($data, Message $message)
+ {
+ return $this->parse($data, $message);
+ }
+
+ private function parse($data, Message $message)
+ {
+ $message->data .= $data;
+
+ if (!$message->header->get('id')) {
+ if (!$this->parseHeader($message)) {
+ return;
+ }
+ }
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ if (!$this->parseQuestion($message)) {
+ return;
+ }
+ }
+
+ if ($message->header->get('anCount') != count($message->answers)) {
+ if (!$this->parseAnswer($message)) {
+ return;
+ }
+ }
+
+ return $message;
+ }
+
+ public function parseHeader(Message $message)
+ {
+ if (strlen($message->data) < 12) {
+ return;
+ }
+
+ $header = substr($message->data, 0, 12);
+ $message->consumed += 12;
+
+ list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header));
+
+ $rcode = $fields & bindec('1111');
+ $z = ($fields >> 4) & bindec('111');
+ $ra = ($fields >> 7) & 1;
+ $rd = ($fields >> 8) & 1;
+ $tc = ($fields >> 9) & 1;
+ $aa = ($fields >> 10) & 1;
+ $opcode = ($fields >> 11) & bindec('1111');
+ $qr = ($fields >> 15) & 1;
+
+ $vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount',
+ 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode');
+
+
+ foreach ($vars as $name => $value) {
+ $message->header->set($name, $value);
+ }
+
+ return $message;
+ }
+
+ public function parseQuestion(Message $message)
+ {
+ if (strlen($message->data) < 2) {
+ return;
+ }
+
+ $consumed = $message->consumed;
+
+ list($labels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ if (null === $labels) {
+ return;
+ }
+
+ if (strlen($message->data) - $consumed < 4) {
+ return;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ $message->consumed = $consumed;
+
+ $message->questions[] = array(
+ 'name' => implode('.', $labels),
+ 'type' => $type,
+ 'class' => $class,
+ );
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ return $this->parseQuestion($message);
+ }
+
+ return $message;
+ }
+
+ public function parseAnswer(Message $message)
+ {
+ if (strlen($message->data) < 2) {
+ return;
+ }
+
+ $consumed = $message->consumed;
+
+ list($labels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ if (null === $labels) {
+ return;
+ }
+
+ if (strlen($message->data) - $consumed < 10) {
+ return;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
+ $consumed += 2;
+
+ $rdata = null;
+
+ if (Message::TYPE_A === $type || Message::TYPE_AAAA === $type) {
+ $ip = substr($message->data, $consumed, $rdLength);
+ $consumed += $rdLength;
+
+ $rdata = inet_ntop($ip);
+ }
+
+ if (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type) {
+ list($bodyLabels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ $rdata = implode('.', $bodyLabels);
+ }
+
+ $message->consumed = $consumed;
+
+ $name = implode('.', $labels);
+ $ttl = $this->signedLongToUnsignedLong($ttl);
+ $record = new Record($name, $type, $class, $ttl, $rdata);
+
+ $message->answers[] = $record;
+
+ if ($message->header->get('anCount') != count($message->answers)) {
+ return $this->parseAnswer($message);
+ }
+
+ return $message;
+ }
+
+ private function readLabels($data, $consumed)
+ {
+ $labels = array();
+
+ while (true) {
+ if ($this->isEndOfLabels($data, $consumed)) {
+ $consumed += 1;
+ break;
+ }
+
+ if ($this->isCompressedLabel($data, $consumed)) {
+ list($newLabels, $consumed) = $this->getCompressedLabel($data, $consumed);
+ $labels = array_merge($labels, $newLabels);
+ break;
+ }
+
+ $length = ord(substr($data, $consumed, 1));
+ $consumed += 1;
+
+ if (strlen($data) - $consumed < $length) {
+ return array(null, null);
+ }
+
+ $labels[] = substr($data, $consumed, $length);
+ $consumed += $length;
+ }
+
+ return array($labels, $consumed);
+ }
+
+ public function isEndOfLabels($data, $consumed)
+ {
+ $length = ord(substr($data, $consumed, 1));
+ return 0 === $length;
+ }
+
+ public function getCompressedLabel($data, $consumed)
+ {
+ list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
+ list($labels) = $this->readLabels($data, $nameOffset);
+
+ return array($labels, $consumed);
+ }
+
+ public function isCompressedLabel($data, $consumed)
+ {
+ $mask = 0xc000; // 1100000000000000
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return (bool) ($peek & $mask);
+ }
+
+ public function getCompressedLabelOffset($data, $consumed)
+ {
+ $mask = 0x3fff; // 0011111111111111
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return array($peek & $mask, $consumed + 2);
+ }
+
+ public function signedLongToUnsignedLong($i)
+ {
+ return $i & 0x80000000 ? $i - 0xffffffff : $i;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/CachedExecutor.php b/assets/php/vendor/react/dns/src/Query/CachedExecutor.php
new file mode 100644
index 0000000..285936d
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/CachedExecutor.php
@@ -0,0 +1,55 @@
+executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $executor = $this->executor;
+ $cache = $this->cache;
+
+ return $this->cache
+ ->lookup($query)
+ ->then(
+ function ($cachedRecords) use ($query) {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ },
+ function () use ($executor, $cache, $nameserver, $query) {
+ return $executor
+ ->query($nameserver, $query)
+ ->then(function ($response) use ($cache, $query) {
+ $cache->storeResponseMessage($query->currentTime, $response);
+ return $response;
+ });
+ }
+ );
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function buildResponse(Query $query, array $cachedRecords)
+ {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/CancellationException.php b/assets/php/vendor/react/dns/src/Query/CancellationException.php
new file mode 100644
index 0000000..ac30f4c
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/CancellationException.php
@@ -0,0 +1,7 @@
+loop = $loop;
+ $this->parser = $parser;
+ $this->dumper = $dumper;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ $transport = strlen($queryData) > 512 ? 'tcp' : 'udp';
+
+ return $this->doQuery($nameserver, $transport, $queryData, $query->name);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function prepareRequest(Query $query)
+ {
+ return Message::createRequestForQuery($query);
+ }
+
+ public function doQuery($nameserver, $transport, $queryData, $name)
+ {
+ // we only support UDP right now
+ if ($transport !== 'udp') {
+ return Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version'
+ ));
+ }
+
+ $that = $this;
+ $parser = $this->parser;
+ $loop = $this->loop;
+
+ // UDP connections are instant, so try this without a timer
+ try {
+ $conn = $this->createConnection($nameserver, $transport);
+ } catch (\Exception $e) {
+ return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e));
+ }
+
+ $deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) {
+ $reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
+
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+ $conn->close();
+ });
+
+ $timer = null;
+ if ($this->timeout !== null) {
+ $timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) {
+ $conn->close();
+ $deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
+ });
+ }
+
+ $conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) {
+ $conn->end();
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ $deferred->reject($e);
+ return;
+ }
+
+ if ($response->header->isTruncated()) {
+ $deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+ $conn->write($queryData);
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * @param string $nameserver
+ * @param string $transport
+ * @return \React\Stream\DuplexStreamInterface
+ */
+ protected function createConnection($nameserver, $transport)
+ {
+ $fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
+ if ($fd === false) {
+ throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno);
+ }
+
+ // Instantiate stream instance around this stream resource.
+ // This ought to be replaced with a datagram socket in the future.
+ // Temporary work around for Windows 10: buffer whole UDP response
+ // @coverageIgnoreStart
+ if (!class_exists('React\Stream\Stream')) {
+ // prefer DuplexResourceStream as of react/stream v0.7.0
+ $conn = new DuplexResourceStream($fd, $this->loop, -1);
+ } else {
+ // use legacy Stream class for react/stream < v0.7.0
+ $conn = new Stream($fd, $this->loop);
+ $conn->bufferSize = null;
+ }
+ // @coverageIgnoreEnd
+
+ return $conn;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php b/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php
new file mode 100644
index 0000000..2f7a635
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/ExecutorInterface.php
@@ -0,0 +1,8 @@
+hosts = $hosts;
+ $this->fallback = $fallback;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
+ // forward lookup for type A or AAAA
+ $records = array();
+ $expectsColon = $query->type === Message::TYPE_AAAA;
+ foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
+ // ensure this is an IPv4/IPV6 address according to query type
+ if ((strpos($ip, ':') !== false) === $expectsColon) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
+ }
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
+ // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
+ $ip = $this->getIpFromHost($query->name);
+
+ if ($ip !== null) {
+ $records = array();
+ foreach ($this->hosts->getHostsForIp($ip) as $host) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $host);
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ }
+ }
+
+ return $this->fallback->query($nameserver, $query);
+ }
+
+ private function getIpFromHost($host)
+ {
+ if (substr($host, -13) === '.in-addr.arpa') {
+ // IPv4: read as IP and reverse bytes
+ $ip = @inet_pton(substr($host, 0, -13));
+ if ($ip === false || isset($ip[4])) {
+ return null;
+ }
+
+ return inet_ntop(strrev($ip));
+ } elseif (substr($host, -9) === '.ip6.arpa') {
+ // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
+ $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
+ if ($ip === false) {
+ return null;
+ }
+
+ return $ip;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/Query.php b/assets/php/vendor/react/dns/src/Query/Query.php
new file mode 100644
index 0000000..aef6e05
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/Query.php
@@ -0,0 +1,19 @@
+name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->currentTime = $currentTime;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/RecordBag.php b/assets/php/vendor/react/dns/src/Query/RecordBag.php
new file mode 100644
index 0000000..358cf5d
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/RecordBag.php
@@ -0,0 +1,27 @@
+records[$record->data] = array($currentTime + $record->ttl, $record);
+ }
+
+ public function all()
+ {
+ return array_values(array_map(
+ function ($value) {
+ list($expiresAt, $record) = $value;
+ return $record;
+ },
+ $this->records
+ ));
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/RecordCache.php b/assets/php/vendor/react/dns/src/Query/RecordCache.php
new file mode 100644
index 0000000..b8142d3
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/RecordCache.php
@@ -0,0 +1,82 @@
+cache = $cache;
+ }
+
+ public function lookup(Query $query)
+ {
+ $id = $this->serializeQueryToIdentity($query);
+
+ $expiredAt = $this->expiredAt;
+
+ return $this->cache
+ ->get($id)
+ ->then(function ($value) use ($query, $expiredAt) {
+ $recordBag = unserialize($value);
+
+ if (null !== $expiredAt && $expiredAt <= $query->currentTime) {
+ return Promise\reject();
+ }
+
+ return $recordBag->all();
+ });
+ }
+
+ public function storeResponseMessage($currentTime, Message $message)
+ {
+ foreach ($message->answers as $record) {
+ $this->storeRecord($currentTime, $record);
+ }
+ }
+
+ public function storeRecord($currentTime, Record $record)
+ {
+ $id = $this->serializeRecordToIdentity($record);
+
+ $cache = $this->cache;
+
+ $this->cache
+ ->get($id)
+ ->then(
+ function ($value) {
+ return unserialize($value);
+ },
+ function ($e) {
+ return new RecordBag();
+ }
+ )
+ ->then(function ($recordBag) use ($id, $currentTime, $record, $cache) {
+ $recordBag->set($currentTime, $record);
+ $cache->set($id, serialize($recordBag));
+ });
+ }
+
+ public function expire($currentTime)
+ {
+ $this->expiredAt = $currentTime;
+ }
+
+ public function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+
+ public function serializeRecordToIdentity(Record $record)
+ {
+ return sprintf('%s:%s:%s', $record->name, $record->type, $record->class);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/RetryExecutor.php b/assets/php/vendor/react/dns/src/Query/RetryExecutor.php
new file mode 100644
index 0000000..90353e5
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/RetryExecutor.php
@@ -0,0 +1,44 @@
+executor = $executor;
+ $this->retries = $retries;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return $this->tryQuery($nameserver, $query, $this->retries);
+ }
+
+ public function tryQuery($nameserver, Query $query, $retries)
+ {
+ $that = $this;
+ $errorback = function ($error) use ($nameserver, $query, $retries, $that) {
+ if (!$error instanceof TimeoutException) {
+ throw $error;
+ }
+ if (0 >= $retries) {
+ throw new \RuntimeException(
+ sprintf("DNS query for %s failed: too many retries", $query->name),
+ 0,
+ $error
+ );
+ }
+ return $that->tryQuery($nameserver, $query, $retries-1);
+ };
+
+ return $this->executor
+ ->query($nameserver, $query)
+ ->then(null, $errorback);
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Query/TimeoutException.php b/assets/php/vendor/react/dns/src/Query/TimeoutException.php
new file mode 100644
index 0000000..90bf806
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Query/TimeoutException.php
@@ -0,0 +1,7 @@
+executor = $executor;
+ $this->loop = $loop;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) {
+ if ($e instanceof Timer\TimeoutException) {
+ $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e);
+ }
+ throw $e;
+ });
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/RecordNotFoundException.php b/assets/php/vendor/react/dns/src/RecordNotFoundException.php
new file mode 100644
index 0000000..0028413
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/RecordNotFoundException.php
@@ -0,0 +1,7 @@
+addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null)
+ {
+ if (!($cache instanceof CacheInterface)) {
+ $cache = new ArrayCache();
+ }
+
+ $nameserver = $this->addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ /**
+ * Tries to load the hosts file and decorates the given executor on success
+ *
+ * @param ExecutorInterface $executor
+ * @return ExecutorInterface
+ * @codeCoverageIgnore
+ */
+ private function decorateHostsFileExecutor(ExecutorInterface $executor)
+ {
+ try {
+ $executor = new HostsFileExecutor(
+ HostsFile::loadFromPathBlocking(),
+ $executor
+ );
+ } catch (\RuntimeException $e) {
+ // ignore this file if it can not be loaded
+ }
+
+ // Windows does not store localhost in hosts file by default but handles this internally
+ // To compensate for this, we explicitly use hard-coded defaults for localhost
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $executor = new HostsFileExecutor(
+ new HostsFile("127.0.0.1 localhost\n::1 localhost"),
+ $executor
+ );
+ }
+
+ return $executor;
+ }
+
+ protected function createExecutor(LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new Executor($loop, new Parser(), new BinaryDumper(), null),
+ 5.0,
+ $loop
+ );
+ }
+
+ protected function createRetryExecutor(LoopInterface $loop)
+ {
+ return new RetryExecutor($this->createExecutor($loop));
+ }
+
+ protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
+ {
+ return new CachedExecutor($this->createRetryExecutor($loop), new RecordCache($cache));
+ }
+
+ protected function addPortToServerIfMissing($nameserver)
+ {
+ if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+ // assume a dummy scheme when checking for the port, otherwise parse_url() fails
+ if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) {
+ $nameserver .= ':53';
+ }
+
+ return $nameserver;
+ }
+}
diff --git a/assets/php/vendor/react/dns/src/Resolver/Resolver.php b/assets/php/vendor/react/dns/src/Resolver/Resolver.php
new file mode 100644
index 0000000..4a4983a
--- /dev/null
+++ b/assets/php/vendor/react/dns/src/Resolver/Resolver.php
@@ -0,0 +1,100 @@
+nameserver = $nameserver;
+ $this->executor = $executor;
+ }
+
+ public function resolve($domain)
+ {
+ $query = new Query($domain, Message::TYPE_A, Message::CLASS_IN, time());
+ $that = $this;
+
+ return $this->executor
+ ->query($this->nameserver, $query)
+ ->then(function (Message $response) use ($query, $that) {
+ return $that->extractAddress($query, $response);
+ });
+ }
+
+ public function extractAddress(Query $query, Message $response)
+ {
+ $answers = $response->answers;
+
+ $addresses = $this->resolveAliases($answers, $query->name);
+
+ if (0 === count($addresses)) {
+ $message = 'DNS Request did not return valid answer.';
+ throw new RecordNotFoundException($message);
+ }
+
+ $address = $addresses[array_rand($addresses)];
+ return $address;
+ }
+
+ public function resolveAliases(array $answers, $name)
+ {
+ $named = $this->filterByName($answers, $name);
+ $aRecords = $this->filterByType($named, Message::TYPE_A);
+ $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
+
+ if ($aRecords) {
+ return $this->mapRecordData($aRecords);
+ }
+
+ if ($cnameRecords) {
+ $aRecords = array();
+
+ $cnames = $this->mapRecordData($cnameRecords);
+ foreach ($cnames as $cname) {
+ $targets = $this->filterByName($answers, $cname);
+ $aRecords = array_merge(
+ $aRecords,
+ $this->resolveAliases($answers, $cname)
+ );
+ }
+
+ return $aRecords;
+ }
+
+ return array();
+ }
+
+ private function filterByName(array $answers, $name)
+ {
+ return $this->filterByField($answers, 'name', $name);
+ }
+
+ private function filterByType(array $answers, $type)
+ {
+ return $this->filterByField($answers, 'type', $type);
+ }
+
+ private function filterByField(array $answers, $field, $value)
+ {
+ $value = strtolower($value);
+ return array_filter($answers, function ($answer) use ($field, $value) {
+ return $value === strtolower($answer->$field);
+ });
+ }
+
+ private function mapRecordData(array $records)
+ {
+ return array_map(function ($record) {
+ return $record->data;
+ }, $records);
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/CallableStub.php b/assets/php/vendor/react/dns/tests/CallableStub.php
new file mode 100644
index 0000000..a34a263
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/CallableStub.php
@@ -0,0 +1,10 @@
+assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsDefaultPath()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Not supported on Windows');
+ }
+
+ $config = Config::loadResolvConfBlocking();
+
+ $this->assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsFromExplicitPath()
+ {
+ $config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf');
+
+ $this->assertEquals(array('8.8.8.8'), $config->nameservers);
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadThrowsWhenPathIsInvalid()
+ {
+ Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf');
+ }
+
+ public function testParsesSingleEntryFile()
+ {
+ $contents = 'nameserver 8.8.8.8';
+ $expected = array('8.8.8.8');
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesNameserverEntriesFromAverageFileCorrectly()
+ {
+ $contents = '#
+# Mac OS X Notice
+#
+# This file is not used by the host name and address resolution
+# or the DNS query routing mechanisms used by most processes on
+# this Mac OS X system.
+#
+# This file is automatically generated.
+#
+domain v.cablecom.net
+nameserver 127.0.0.1
+nameserver ::1
+';
+ $expected = array('127.0.0.1', '::1');
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesEmptyFileWithoutNameserverEntries()
+ {
+ $contents = '';
+ $expected = array();
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,');
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries()
+ {
+ $contents = '
+# nameserver 1.2.3.4
+; nameserver 2.3.4.5
+
+nameserver 3.4.5.6 # nope
+nameserver 4.5.6.7 5.6.7.8
+ nameserver 6.7.8.9
+NameServer 7.8.9.10
+';
+ $expected = array();
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsFromWmicOnWindows()
+ {
+ if (DIRECTORY_SEPARATOR !== '\\') {
+ $this->markTestSkipped('Only on Windows');
+ }
+
+ $config = Config::loadWmicBlocking();
+
+ $this->assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsSingleEntryFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1}
+ACE,
+';
+ $expected = array('192.168.2.1');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsEmptyListFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+';
+ $expected = array();
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsSingleEntryForMultipleNicsFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1}
+ACE,
+ACE,{192.168.2.2}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1;192.168.2.2}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{"192.168.2.1","192.168.2.2"}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ private function echoCommand($output)
+ {
+ return 'echo ' . escapeshellarg($output);
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php b/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php
new file mode 100644
index 0000000..bb9eac7
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Config/FilesystemFactoryTest.php
@@ -0,0 +1,70 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $factory = new FilesystemFactory($loop);
+ $factory->parseEtcResolvConf($contents)->then(function ($config) use (&$capturedConfig) {
+ $capturedConfig = $config;
+ });
+
+ $this->assertNotNull($capturedConfig);
+ $this->assertSame($expected, $capturedConfig->nameservers);
+ }
+
+ /** @test */
+ public function createShouldLoadStuffFromFilesystem()
+ {
+ $this->markTestIncomplete('Filesystem API is incomplete');
+
+ $expected = array('8.8.8.8');
+
+ $triggerListener = null;
+ $capturedConfig = null;
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop
+ ->expects($this->once())
+ ->method('addReadStream')
+ ->will($this->returnCallback(function ($stream, $listener) use (&$triggerListener) {
+ $triggerListener = function () use ($stream, $listener) {
+ call_user_func($listener, $stream);
+ };
+ }));
+
+ $factory = new FilesystemFactory($loop);
+ $factory->create(__DIR__.'/../Fixtures/etc/resolv.conf')->then(function ($config) use (&$capturedConfig) {
+ $capturedConfig = $config;
+ });
+
+ $triggerListener();
+
+ $this->assertNotNull($capturedConfig);
+ $this->assertSame($expected, $capturedConfig->nameservers);
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php b/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php
new file mode 100644
index 0000000..ff74ad2
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Config/HostsFileTest.php
@@ -0,0 +1,170 @@
+assertInstanceOf('React\Dns\Config\HostsFile', $hosts);
+ }
+
+ public function testDefaultShouldHaveLocalhostMapped()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Not supported on Windows');
+ }
+
+ $hosts = HostsFile::loadFromPathBlocking();
+
+ $this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost'));
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadThrowsForInvalidPath()
+ {
+ HostsFile::loadFromPathBlocking('does/not/exist');
+ }
+
+ public function testContainsSingleLocalhostEntry()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testNonIpReturnsNothingForInvalidHosts()
+ {
+ $hosts = new HostsFile('a b');
+
+ $this->assertEquals(array(), $hosts->getIpsForHost('a'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('b'));
+ }
+
+ public function testIgnoresIpv6ZoneId()
+ {
+ $hosts = new HostsFile('fe80::1%lo0 localhost');
+
+ $this->assertEquals(array('fe80::1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testSkipsComments()
+ {
+ $hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com');
+
+ $this->assertEquals(array('127.0.0.2'), $hosts->getIpsForHost('localhost'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testContainsSingleLocalhostEntryWithCaseIgnored()
+ {
+ $hosts = new HostsFile('127.0.0.1 LocalHost');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('LOCALHOST'));
+ }
+
+ public function testEmptyFileContainsNothing()
+ {
+ $hosts = new HostsFile('');
+
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testSingleEntryWithMultipleNames()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost example.com');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('example.com'));
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testMergesEntriesOverMultipleLines()
+ {
+ $hosts = new HostsFile("127.0.0.1 localhost\n127.0.0.2 localhost\n127.0.0.3 a localhost b\n127.0.0.4 a localhost");
+
+ $this->assertEquals(array('127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testMergesIpv4AndIpv6EntriesOverMultipleLines()
+ {
+ $hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost");
+
+ $this->assertEquals(array('127.0.0.1', '::1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testReverseLookup()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('192.168.1.1'));
+ }
+
+ public function testReverseSkipsComments()
+ {
+ $hosts = new HostsFile("# start\n#127.0.0.1 localhosted\n127.0.0.2\tlocalhost\t# example.com\n\t127.0.0.3\t\texample.org\t\t");
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1'));
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.2'));
+ $this->assertEquals(array('example.org'), $hosts->getHostsForIp('127.0.0.3'));
+ }
+
+ public function testReverseNonIpReturnsNothing()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('localhost'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1.1'));
+ }
+
+ public function testReverseNonIpReturnsNothingForInvalidHosts()
+ {
+ $hosts = new HostsFile('a b');
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('a'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('b'));
+ }
+
+ public function testReverseLookupReturnsLowerCaseHost()
+ {
+ $hosts = new HostsFile('127.0.0.1 LocalHost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
+ }
+
+ public function testReverseLookupChecksNormalizedIpv6()
+ {
+ $hosts = new HostsFile('FE80::00a1 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::A1'));
+ }
+
+ public function testReverseLookupIgnoresIpv6ZoneId()
+ {
+ $hosts = new HostsFile('fe80::1%lo0 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::1'));
+ }
+
+ public function testReverseLookupReturnsMultipleHostsOverSingleLine()
+ {
+ $hosts = new HostsFile("::1 ip6-localhost ip6-loopback");
+
+ $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
+ }
+
+ public function testReverseLookupReturnsMultipleHostsOverMultipleLines()
+ {
+ $hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback");
+
+ $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf b/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf
new file mode 100644
index 0000000..cae093a
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Fixtures/etc/resolv.conf
@@ -0,0 +1 @@
+nameserver 8.8.8.8
diff --git a/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php b/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php
new file mode 100644
index 0000000..0807e86
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/FunctionalResolverTest.php
@@ -0,0 +1,71 @@
+loop = LoopFactory::create();
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('8.8.8.8', $this->loop);
+ }
+
+ public function testResolveLocalhostResolves()
+ {
+ $promise = $this->resolver->resolve('localhost');
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveGoogleResolves()
+ {
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveInvalidRejects()
+ {
+ $promise = $this->resolver->resolve('example.invalid');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $this->loop->run();
+ }
+
+ public function testResolveCancelledRejectsImmediately()
+ {
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ $promise->cancel();
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.1, $time);
+ }
+
+ public function testInvalidResolverDoesNotResolveGoogle()
+ {
+ $factory = new Factory();
+ $this->resolver = $factory->create('255.255.255.255', $this->loop);
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Model/MessageTest.php b/assets/php/vendor/react/dns/tests/Model/MessageTest.php
new file mode 100644
index 0000000..53d6b28
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Model/MessageTest.php
@@ -0,0 +1,30 @@
+assertTrue($request->header->isQuery());
+ $this->assertSame(1, $request->header->get('rd'));
+ }
+
+ public function testCreateResponseWithNoAnswers()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $answers = array();
+ $request = Message::createResponseWithAnswersForQuery($query, $answers);
+
+ $this->assertFalse($request->header->isQuery());
+ $this->assertTrue($request->header->isResponse());
+ $this->assertEquals(0, $request->header->get('anCount'));
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php b/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php
new file mode 100644
index 0000000..bf60ca9
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Protocol/BinaryDumperTest.php
@@ -0,0 +1,48 @@
+formatHexDump(str_replace(' ', '', $data), 2);
+
+ $request = new Message();
+ $request->header->set('id', 0x7262);
+ $request->header->set('rd', 1);
+
+ $request->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN,
+ );
+
+ $request->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($request);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ private function convertBinaryToHexDump($input)
+ {
+ return $this->formatHexDump(implode('', unpack('H*', $input)));
+ }
+
+ private function formatHexDump($input)
+ {
+ return implode(' ', str_split($input, 2));
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php b/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php
new file mode 100644
index 0000000..195fad2
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Protocol/ParserTest.php
@@ -0,0 +1,343 @@
+parser = new Parser();
+ }
+
+ /**
+ * @dataProvider provideConvertTcpDumpToBinary
+ */
+ public function testConvertTcpDumpToBinary($expected, $data)
+ {
+ $this->assertSame($expected, $this->convertTcpDumpToBinary($data));
+ }
+
+ public function provideConvertTcpDumpToBinary()
+ {
+ return array(
+ array(chr(0x72).chr(0x62), "72 62"),
+ array(chr(0x72).chr(0x62).chr(0x01).chr(0x00), "72 62 01 00"),
+ array(chr(0x72).chr(0x62).chr(0x01).chr(0x00).chr(0x00).chr(0x01), "72 62 01 00 00 01"),
+ array(chr(0x01).chr(0x00).chr(0x01), "01 00 01"),
+ );
+ }
+
+ public function testParseRequest()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $request = $this->parser->parseMessage($data);
+
+ $header = $request->header;
+ $this->assertSame(0x7262, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(0, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(0, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(0, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $request->questions);
+ $this->assertSame('igor.io', $request->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']);
+ }
+
+ public function testParseResponse()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0x7262, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('igor.io', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseQuestionWithTwoQuestions()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "03 77 77 77 04 69 67 6f 72 02 69 6f 00"; // question: www.igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $request = new Message();
+ $request->header->set('qdCount', 2);
+ $request->data = $data;
+
+ $this->parser->parseQuestion($request);
+
+ $this->assertCount(2, $request->questions);
+ $this->assertSame('igor.io', $request->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']);
+ $this->assertSame('www.igor.io', $request->questions[1]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[1]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[1]['class']);
+ }
+
+ public function testParseAnswerWithInlineData()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseResponseWithCnameAndOffsetPointers()
+ {
+ $data = "";
+ $data .= "9e 8d 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 6d 61 69 6c 06 67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: mail.google.com
+ $data .= "00 05 00 01"; // question: type CNAME, class IN
+ $data .= "c0 0c"; // answer: offset pointer to mail.google.com
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 a8 9c"; // answer: ttl 43164
+ $data .= "00 0f"; // answer: rdlength 15
+ $data .= "0a 67 6f 6f 67 6c 65 6d 61 69 6c 01 6c"; // answer: rdata googlemail.l.
+ $data .= "c0 11"; // answer: rdata offset pointer to google.com
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('mail.google.com', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_CNAME, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('mail.google.com', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(43164, $response->answers[0]->ttl);
+ $this->assertSame('googlemail.l.google.com', $response->answers[0]->data);
+ }
+
+ public function testParseAAAAResponse()
+ {
+ $data = "";
+ $data .= "cd 72 81 80 00 01 00 01 00 00 00 00 06"; // header
+ $data .= "67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: google.com
+ $data .= "00 1c 00 01"; // question: type AAAA, class IN
+ $data .= "c0 0c"; // answer: offset pointer to google.com
+ $data .= "00 1c 00 01"; // answer: type AAAA, class IN
+ $data .= "00 00 01 2b"; // answer: ttl 299
+ $data .= "00 10"; // answer: rdlength 16
+ $data .= "2a 00 14 50 40 09 08 09 00 00 00 00 00 00 20 0e"; // answer: 2a00:1450:4009:809::200e
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0xcd72, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('google.com', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_AAAA, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('google.com', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_AAAA, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(299, $response->answers[0]->ttl);
+ $this->assertSame('2a00:1450:4009:809::200e', $response->answers[0]->data);
+ }
+
+ public function testParseResponseWithTwoAnswers()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 02 00 00 00 00"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 00 29"; // answer: ttl 41
+ $data .= "00 0e"; // answer: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io
+ $data .= "c0 32"; // answer: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 0d f7"; // answer: ttl 3575
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "c1 df 4e 98"; // answer: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(2, $response->answers);
+
+ $this->assertSame('io.whois-servers.net', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(41, $response->answers[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->answers[0]->data);
+
+ $this->assertSame('whois.nic.io', $response->answers[1]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[1]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[1]->class);
+ $this->assertSame(3575, $response->answers[1]->ttl);
+ $this->assertSame('193.223.78.152', $response->answers[1]->data);
+ }
+
+ public function testParsePTRResponse()
+ {
+ $data = "";
+ $data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "01 34 01 34 01 38 01 38 07 69 6e"; // question: 4.4.8.8.in-addr.arpa
+ $data .= "2d 61 64 64 72 04 61 72 70 61 00"; // question (continued)
+ $data .= "00 0c 00 01"; // question: type PTR, class IN
+ $data .= "c0 0c"; // answer: offset pointer to rdata
+ $data .= "00 0c 00 01"; // answer: type PTR, class IN
+ $data .= "00 01 51 7f"; // answer: ttl 86399
+ $data .= "00 20"; // answer: rdlength 32
+ $data .= "13 67 6f 6f 67 6c 65 2d 70 75 62 6c 69 63 2d 64"; // answer: rdata google-public-dns-b.google.com.
+ $data .= "6e 73 2d 62 06 67 6f 6f 67 6c 65 03 63 6f 6d 00";
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0x5dd8, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('4.4.8.8.in-addr.arpa', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_PTR, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('4.4.8.8.in-addr.arpa', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_PTR, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86399, $response->answers[0]->ttl);
+ $this->assertSame('google-public-dns-b.google.com', $response->answers[0]->data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncomplete()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ //$data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ private function convertTcpDumpToBinary($input)
+ {
+ // sudo ngrep -d en1 -x port 53
+
+ return pack('H*', str_replace(' ', '', $input));
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php
new file mode 100644
index 0000000..d08ed05
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Query/CachedExecutorTest.php
@@ -0,0 +1,100 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue($this->createPromiseMock()));
+
+ $cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cache
+ ->expects($this->once())
+ ->method('lookup')
+ ->will($this->returnValue(Promise\reject()));
+ $cachedExecutor = new CachedExecutor($executor, $cache);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $cachedExecutor->query('8.8.8.8', $query);
+ }
+
+ /**
+ * @covers React\Dns\Query\CachedExecutor
+ * @test
+ */
+ public function callingQueryTwiceShouldUseCachedResult()
+ {
+ $cachedRecords = array(new Record('igor.io', Message::TYPE_A, Message::CLASS_IN));
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->callQueryCallbackWithAddress('178.79.169.131'));
+
+ $cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cache
+ ->expects($this->at(0))
+ ->method('lookup')
+ ->with($this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue(Promise\reject()));
+ $cache
+ ->expects($this->at(1))
+ ->method('storeResponseMessage')
+ ->with($this->isType('integer'), $this->isInstanceOf('React\Dns\Model\Message'));
+ $cache
+ ->expects($this->at(2))
+ ->method('lookup')
+ ->with($this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue(Promise\resolve($cachedRecords)));
+
+ $cachedExecutor = new CachedExecutor($executor, $cache);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
+ $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
+ }
+
+ private function callQueryCallbackWithAddress($address)
+ {
+ return $this->returnCallback(function ($nameserver, $query) use ($address) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, $address);
+
+ return Promise\resolve($response);
+ });
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+
+ private function createPromiseMock()
+ {
+ return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php
new file mode 100644
index 0000000..0d7ac1d
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Query/ExecutorTest.php
@@ -0,0 +1,308 @@
+loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->parser = $this->getMockBuilder('React\Dns\Protocol\Parser')->getMock();
+ $this->dumper = new BinaryDumper();
+
+ $this->executor = new Executor($this->loop, $this->parser, $this->dumper);
+ }
+
+ /** @test */
+ public function queryShouldCreateUdpRequest()
+ {
+ $timer = $this->createTimerMock();
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock(false));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldRejectIfRequestIsLargerThan512Bytes()
+ {
+ $query = new Query(str_repeat('a', 512).'.igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->setExpectedException('RuntimeException', 'DNS query for ' . $query->name . ' failed: Requested transport "tcp" not available, only UDP is supported in this version');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldCloseConnectionWhenCancelled()
+ {
+ $conn = $this->createConnectionMock(false);
+ $conn->expects($this->once())->method('close');
+
+ $timer = $this->createTimerMock();
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnValue($conn));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->cancel();
+
+ $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldNotStartOrCancelTimerWhenCancelledWithTimeoutIsNull()
+ {
+ $this->loop
+ ->expects($this->never())
+ ->method('addTimer');
+
+ $this->executor = new Executor($this->loop, $this->parser, $this->dumper, null);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('127.0.0.1:53', $query);
+
+ $promise->cancel();
+
+ $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldRejectIfResponseIsTruncated()
+ {
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->parser
+ ->expects($this->once())
+ ->method('parseMessage')
+ ->will($this->returnTruncatedResponse());
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock());
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldFailIfUdpThrow()
+ {
+ $this->loop
+ ->expects($this->never())
+ ->method('addTimer');
+
+ $this->parser
+ ->expects($this->never())
+ ->method('parseMessage');
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->throwException(new \Exception('Nope')));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->setExpectedException('RuntimeException', 'DNS query for igor.io failed: Nope');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldCancelTimerWhenFullResponseIsReceived()
+ {
+ $conn = $this->createConnectionMock();
+
+ $this->parser
+ ->expects($this->once())
+ ->method('parseMessage')
+ ->will($this->returnStandardResponse());
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->at(0))
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock());
+
+
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->once())
+ ->method('addTimer')
+ ->with(5, $this->isInstanceOf('Closure'))
+ ->will($this->returnValue($timer));
+
+ $this->loop
+ ->expects($this->once())
+ ->method('cancelTimer')
+ ->with($timer);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldCloseConnectionOnTimeout()
+ {
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->at(0))
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock(false));
+
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->never())
+ ->method('cancelTimer');
+
+ $this->loop
+ ->expects($this->once())
+ ->method('addTimer')
+ ->with(5, $this->isInstanceOf('Closure'))
+ ->will($this->returnCallback(function ($time, $callback) use (&$timerCallback, $timer) {
+ $timerCallback = $callback;
+ return $timer;
+ }));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->assertNotNull($timerCallback);
+ $timerCallback();
+
+ $this->setExpectedException('React\Dns\Query\TimeoutException', 'DNS query for igor.io timed out');
+ Block\await($promise, $this->loop);
+ }
+
+ private function returnStandardResponse()
+ {
+ $that = $this;
+ $callback = function ($data) use ($that) {
+ $response = new Message();
+ $that->convertMessageToStandardResponse($response);
+ return $response;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ private function returnTruncatedResponse()
+ {
+ $that = $this;
+ $callback = function ($data) use ($that) {
+ $response = new Message();
+ $that->convertMessageToTruncatedResponse($response);
+ return $response;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ public function convertMessageToStandardResponse(Message $response)
+ {
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->prepare();
+
+ return $response;
+ }
+
+ public function convertMessageToTruncatedResponse(Message $response)
+ {
+ $this->convertMessageToStandardResponse($response);
+ $response->header->set('tc', 1);
+ $response->prepare();
+
+ return $response;
+ }
+
+ private function returnNewConnectionMock($emitData = true)
+ {
+ $conn = $this->createConnectionMock($emitData);
+
+ $callback = function () use ($conn) {
+ return $conn;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ private function createConnectionMock($emitData = true)
+ {
+ $conn = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock();
+ $conn
+ ->expects($this->any())
+ ->method('on')
+ ->with('data', $this->isInstanceOf('Closure'))
+ ->will($this->returnCallback(function ($name, $callback) use ($emitData) {
+ $emitData && $callback(null);
+ }));
+
+ return $conn;
+ }
+
+ private function createTimerMock()
+ {
+ return $this->getMockBuilder(
+ interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface'
+ )->getMock();
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\Executor')
+ ->setConstructorArgs(array($this->loop, $this->parser, $this->dumper))
+ ->setMethods(array('createConnection'))
+ ->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php
new file mode 100644
index 0000000..70d877e
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Query/HostsFileExecutorTest.php
@@ -0,0 +1,126 @@
+hosts = $this->getMockBuilder('React\Dns\Config\HostsFile')->disableOriginalConstructor()->getMock();
+ $this->fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $this->executor = new HostsFileExecutor($this->hosts, $this->fallback);
+ }
+
+ public function testDoesNotTryToGetIpsForMxQuery()
+ {
+ $this->hosts->expects($this->never())->method('getIpsForHost');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_MX, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpsWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array());
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testReturnsResponseMessageIfIpsWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpv4Matches()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
+ $this->fallback->expects($this->once())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testReturnsResponseMessageIfIpv6AddressesWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpv6Matches()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
+ $this->fallback->expects($this->once())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
+ }
+
+ public function testDoesReturnReverseIpv4Lookup()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoReverseIpv4Matches()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array());
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testDoesReturnReverseIpv6Lookup()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackForInvalidAddress()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidIpv4Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidLengthIpv6Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidHexIpv6Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('zZz.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php b/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php
new file mode 100644
index 0000000..83b8934
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Query/RecordBagTest.php
@@ -0,0 +1,64 @@
+assertSame(array(), $recordBag->all());
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldSetTheValue()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600));
+
+ $records = $recordBag->all();
+ $this->assertCount(1, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_A, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldSetManyValues()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
+
+ $records = $recordBag->all();
+ $this->assertCount(2, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_A, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ $this->assertSame('178.79.169.131', $records[0]->data);
+ $this->assertSame('igor.io', $records[1]->name);
+ $this->assertSame(Message::TYPE_A, $records[1]->type);
+ $this->assertSame(Message::CLASS_IN, $records[1]->class);
+ $this->assertSame('178.79.169.132', $records[1]->data);
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php b/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php
new file mode 100644
index 0000000..399fbe8
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Query/RecordCacheTest.php
@@ -0,0 +1,123 @@
+lookup($query);
+
+ $this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordShouldMakeLookupSucceed()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(1, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeTwoRecordsShouldReturnBoth()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(2, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ $this->assertSame('178.79.169.132', $cachedRecords[1]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeResponseMessageShouldStoreAllAnswerValues()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $response = new Message();
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132');
+ $response->prepare();
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeResponseMessage($query->currentTime, $response);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(2, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ $this->assertSame('178.79.169.132', $cachedRecords[1]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function expireShouldExpireDeadRecords()
+ {
+ $cachedTime = 1345656451;
+ $currentTime = $cachedTime + 3605;
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($cachedTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $cache->expire($currentTime);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, $currentTime);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
+ }
+
+ private function getPromiseValue(PromiseInterface $promise)
+ {
+ $capturedValue = null;
+
+ $promise->then(function ($value) use (&$capturedValue) {
+ $capturedValue = $value;
+ });
+
+ return $capturedValue;
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php
new file mode 100644
index 0000000..8950f84
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Query/RetryExecutorTest.php
@@ -0,0 +1,197 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue($this->expectPromiseOnce()));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldRetryQueryOnTimeout()
+ {
+ $response = $this->createStandardResponse();
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(2))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->onConsecutiveCalls(
+ $this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new TimeoutException("timeout"));
+ }),
+ $this->returnCallback(function ($domain, $query) use ($response) {
+ return Promise\resolve($response);
+ })
+ ));
+
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('React\Dns\Model\Message'));
+
+ $errorback = $this->expectCallableNever();
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldStopRetryingAfterSomeAttempts()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(3))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new TimeoutException("timeout"));
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('RuntimeException'));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldForwardNonTimeoutErrors()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new \Exception);
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('Exception'));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldCancelQueryOnCancel()
+ {
+ $cancelled = 0;
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ })
+ );
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+ }
+
+ protected function expectPromiseOnce($return = null)
+ {
+ $mock = $this->createPromiseMock();
+ $mock
+ ->expects($this->once())
+ ->method('then')
+ ->will($this->returnValue($return));
+
+ return $mock;
+ }
+
+ protected function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+
+ protected function createPromiseMock()
+ {
+ return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+ }
+
+ protected function createStandardResponse()
+ {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->prepare();
+
+ return $response;
+ }
+}
+
diff --git a/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php b/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php
new file mode 100644
index 0000000..0d37fb4
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Query/TimeoutExecutorTest.php
@@ -0,0 +1,115 @@
+loop = Factory::create();
+
+ $this->wrapped = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+
+ $this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop);
+ }
+
+ public function testCancellingPromiseWillCancelWrapped()
+ {
+ $cancelled = 0;
+
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ }));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testResolvesPromiseWhenWrappedResolves()
+ {
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->willReturn(Promise\resolve('0.0.0.0'));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+ }
+
+ public function testRejectsPromiseWhenWrappedRejects()
+ {
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->willReturn(Promise\reject(new \RuntimeException()));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException()));
+ }
+
+ public function testWrappedWillBeCancelledOnTimeout()
+ {
+ $this->executor = new TimeoutExecutor($this->wrapped, 0, $this->loop);
+
+ $cancelled = 0;
+
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->logicalAnd(
+ $this->isInstanceOf('React\Dns\Query\TimeoutException'),
+ $this->attribute($this->equalTo('DNS query for igor.io timed out'), 'message')
+ ));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query)->then($callback, $errorback);
+
+ $this->assertEquals(0, $cancelled);
+
+ $this->loop->run();
+
+ $this->assertEquals(1, $cancelled);
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php b/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php
new file mode 100644
index 0000000..acaeac0
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Resolver/FactoryTest.php
@@ -0,0 +1,131 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create('8.8.8.8:53', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ }
+
+ /** @test */
+ public function createWithoutPortShouldCreateResolverWithDefaultPort()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create('8.8.8.8', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $this->assertSame('8.8.8.8:53', $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
+ }
+
+ /** @test */
+ public function createCachedShouldCreateResolverWithCachedExecutor()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->createCached('8.8.8.8:53', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $executor = $this->getResolverPrivateExecutor($resolver);
+ $this->assertInstanceOf('React\Dns\Query\CachedExecutor', $executor);
+ $recordCache = $this->getCachedExecutorPrivateMemberValue($executor, 'cache');
+ $recordCacheCache = $this->getRecordCachePrivateMemberValue($recordCache, 'cache');
+ $this->assertInstanceOf('React\Cache\CacheInterface', $recordCacheCache);
+ $this->assertInstanceOf('React\Cache\ArrayCache', $recordCacheCache);
+ }
+
+ /** @test */
+ public function createCachedShouldCreateResolverWithCachedExecutorWithCustomCache()
+ {
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->createCached('8.8.8.8:53', $loop, $cache);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $executor = $this->getResolverPrivateExecutor($resolver);
+ $this->assertInstanceOf('React\Dns\Query\CachedExecutor', $executor);
+ $recordCache = $this->getCachedExecutorPrivateMemberValue($executor, 'cache');
+ $recordCacheCache = $this->getRecordCachePrivateMemberValue($recordCache, 'cache');
+ $this->assertInstanceOf('React\Cache\CacheInterface', $recordCacheCache);
+ $this->assertSame($cache, $recordCacheCache);
+ }
+
+ /**
+ * @test
+ * @dataProvider factoryShouldAddDefaultPortProvider
+ */
+ public function factoryShouldAddDefaultPort($input, $expected)
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create($input, $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $this->assertSame($expected, $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
+ }
+
+ public static function factoryShouldAddDefaultPortProvider()
+ {
+ return array(
+ array('8.8.8.8', '8.8.8.8:53'),
+ array('1.2.3.4:5', '1.2.3.4:5'),
+ array('localhost', 'localhost:53'),
+ array('localhost:1234', 'localhost:1234'),
+ array('::1', '[::1]:53'),
+ array('[::1]:53', '[::1]:53')
+ );
+ }
+
+ private function getResolverPrivateExecutor($resolver)
+ {
+ $executor = $this->getResolverPrivateMemberValue($resolver, 'executor');
+
+ // extract underlying executor that may be wrapped in multiple layers of hosts file executors
+ while ($executor instanceof HostsFileExecutor) {
+ $reflector = new \ReflectionProperty('React\Dns\Query\HostsFileExecutor', 'fallback');
+ $reflector->setAccessible(true);
+
+ $executor = $reflector->getValue($executor);
+ }
+
+ return $executor;
+ }
+
+ private function getResolverPrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Resolver\Resolver', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+
+ private function getCachedExecutorPrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Query\CachedExecutor', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+
+ private function getRecordCachePrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Query\RecordCache', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php b/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
new file mode 100644
index 0000000..b5175e3
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
@@ -0,0 +1,100 @@
+createExecutorMock();
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+
+ $answers = $resolver->resolveAliases($answers, $name);
+
+ $this->assertEquals($expectedAnswers, $answers);
+ }
+
+ public function provideAliasedAnswers()
+ {
+ return array(
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array(),
+ array(
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'baz.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'qux.igor.io'),
+ new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
+ new Record('qux.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
+ ),
+ 'igor.io',
+ ),
+ );
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php b/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php
new file mode 100644
index 0000000..e11509b
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/Resolver/ResolverTest.php
@@ -0,0 +1,129 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableOnceWith('178.79.169.131'));
+ }
+
+ /** @test */
+ public function resolveShouldQueryARecordsAndIgnoreCase()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class);
+ $response->answers[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('blog.wyrihaximus.net')->then($this->expectCallableOnceWith('178.79.169.131'));
+ }
+
+ /** @test */
+ public function resolveShouldFilterByName()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record('foo.bar', $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException'));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ /** @test */
+ public function resolveWithNoAnswersShouldThrowException()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException'));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ /**
+ * @test
+ */
+ public function resolveWithNoAnswersShouldCallErrbackIfGiven()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException'));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/dns/tests/TestCase.php b/assets/php/vendor/react/dns/tests/TestCase.php
new file mode 100644
index 0000000..a5a22bf
--- /dev/null
+++ b/assets/php/vendor/react/dns/tests/TestCase.php
@@ -0,0 +1,61 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Dns\CallableStub')->getMock();
+ }
+
+ public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
+ {
+ if (method_exists($this, 'expectException')) {
+ // PHPUnit 5
+ $this->expectException($exception);
+ if ($exceptionMessage !== '') {
+ $this->expectExceptionMessage($exceptionMessage);
+ }
+ if ($exceptionCode !== null) {
+ $this->expectExceptionCode($exceptionCode);
+ }
+ } else {
+ // legacy PHPUnit 4
+ parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
+ }
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/.gitignore b/assets/php/vendor/react/event-loop/.gitignore
new file mode 100644
index 0000000..81b9258
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor
diff --git a/assets/php/vendor/react/event-loop/.travis.yml b/assets/php/vendor/react/event-loop/.travis.yml
new file mode 100644
index 0000000..7af713a
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/.travis.yml
@@ -0,0 +1,39 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+addons:
+ apt:
+ packages:
+ - libevent-dev # Used by 'event' and 'libevent' PHP extensions
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+install:
+ - ./travis-init.sh
+ - composer install
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/assets/php/vendor/react/event-loop/CHANGELOG.md b/assets/php/vendor/react/event-loop/CHANGELOG.md
new file mode 100644
index 0000000..c291840
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/CHANGELOG.md
@@ -0,0 +1,316 @@
+# Changelog
+
+## 0.5.1 (2018-04-09)
+
+* New loop: ExtEvLoop (PECL ext-ev) (#148 by @kaduev13)
+
+## 0.5.0 (2018-04-05)
+
+A major feature release with a significant documentation overhaul and long overdue API cleanup!
+
+This update involves a number of BC breaks due to dropped support for deprecated
+functionality. We've tried hard to avoid BC breaks where possible and minimize
+impact otherwise. We expect that most consumers of this package will actually
+not be affected by any BC breaks, see below for more details.
+
+We realize that the changes listed below may seem overwhelming, but we've tried
+to be very clear about any possible BC breaks. Don't worry: In fact, all ReactPHP
+components are already compatible and support both this new release as well as
+providing backwards compatibility with the last release.
+
+* Feature / BC break: Add support for signal handling via new
+ `LoopInterface::addSignal()` and `LoopInterface::removeSignal()` methods.
+ (#104 by @WyriHaximus and #111 and #150 by @clue)
+
+ ```php
+ $loop->addSignal(SIGINT, function () {
+ echo 'CTRL-C';
+ });
+ ```
+
+* Feature: Significant documentation updates for `LoopInterface` and `Factory`.
+ (#100, #119, #126, #127, #159 and #160 by @clue, #113 by @WyriHaximus and #81 and #91 by @jsor)
+
+* Feature: Add examples to ease getting started
+ (#99, #100 and #125 by @clue, #59 by @WyriHaximus and #143 by @jsor)
+
+* Feature: Documentation for advanced timer concepts, such as monotonic time source vs wall-clock time
+ and high precision timers with millisecond accuracy or below.
+ (#130 and #157 by @clue)
+
+* Feature: Documentation for advanced stream concepts, such as edge-triggered event listeners
+ and stream buffers and allow throwing Exception if stream resource is not supported.
+ (#129 and #158 by @clue)
+
+* Feature: Throw `BadMethodCallException` on manual loop creation when required extension isn't installed.
+ (#153 by @WyriHaximus)
+
+* Feature / BC break: First class support for legacy PHP 5.3 through PHP 7.2 and HHVM
+ and remove all `callable` type hints for consistency reasons.
+ (#141 and #151 by @clue)
+
+* BC break: Documentation for timer API and clean up unneeded timer API.
+ (#102 by @clue)
+
+ Remove `TimerInterface::cancel()`, use `LoopInterface::cancelTimer()` instead:
+
+ ```php
+ // old (method invoked on timer instance)
+ $timer->cancel();
+
+ // already supported before: invoke method on loop instance
+ $loop->cancelTimer($timer);
+ ```
+
+ Remove unneeded `TimerInterface::setData()` and `TimerInterface::getData()`,
+ use closure binding to add arbitrary data to timer instead:
+
+ ```php
+ // old (limited setData() and getData() only allows single variable)
+ $name = 'Tester';
+ $timer = $loop->addTimer(1.0, function ($timer) {
+ echo 'Hello ' . $timer->getData() . PHP_EOL;
+ });
+ $timer->setData($name);
+
+ // already supported before: closure binding allows any number of variables
+ $name = 'Tester';
+ $loop->addTimer(1.0, function () use ($name) {
+ echo 'Hello ' . $name . PHP_EOL;
+ });
+ ```
+
+ Remove unneeded `TimerInterface::getLoop()`, use closure binding instead:
+
+ ```php
+ // old (getLoop() called on timer instance)
+ $loop->addTimer(0.1, function ($timer) {
+ $timer->getLoop()->stop();
+ });
+
+ // already supported before: use closure binding as usual
+ $loop->addTimer(0.1, function () use ($loop) {
+ $loop->stop();
+ });
+ ```
+
+* BC break: Remove unneeded `LoopInterface::isTimerActive()` and
+ `TimerInterface::isActive()` to reduce API surface.
+ (#133 by @clue)
+
+ ```php
+ // old (method on timer instance or on loop instance)
+ $timer->isActive();
+ $loop->isTimerActive($timer);
+ ```
+
+* BC break: Move `TimerInterface` one level up to `React\EventLoop\TimerInterface`.
+ (#138 by @WyriHaximus)
+
+ ```php
+ // old (notice obsolete "Timer" namespace)
+ assert($timer instanceof React\EventLoop\Timer\TimerInterface);
+
+ // new
+ assert($timer instanceof React\EventLoop\TimerInterface);
+ ```
+
+* BC break: Remove unneeded `LoopInterface::nextTick()` (and internal `NextTickQueue`),
+ use `LoopInterface::futureTick()` instead.
+ (#30 by @clue)
+
+ ```php
+ // old (removed)
+ $loop->nextTick(function () {
+ echo 'tick';
+ });
+
+ // already supported before
+ $loop->futureTick(function () {
+ echo 'tick';
+ });
+ ```
+
+* BC break: Remove unneeded `$loop` argument for `LoopInterface::futureTick()`
+ (and fix internal cyclic dependency).
+ (#103 by @clue)
+
+ ```php
+ // old ($loop gets passed by default)
+ $loop->futureTick(function ($loop) {
+ $loop->stop();
+ });
+
+ // already supported before: use closure binding as usual
+ $loop->futureTick(function () use ($loop) {
+ $loop->stop();
+ });
+ ```
+
+* BC break: Remove unneeded `LoopInterface::tick()`.
+ (#72 by @jsor)
+
+ ```php
+ // old (removed)
+ $loop->tick();
+
+ // suggested work around for testing purposes only
+ $loop->futureTick(function () use ($loop) {
+ $loop->stop();
+ });
+ ```
+
+* BC break: Documentation for advanced stream API and clean up unneeded stream API.
+ (#110 by @clue)
+
+ Remove unneeded `$loop` argument for `LoopInterface::addReadStream()`
+ and `LoopInterface::addWriteStream()`, use closure binding instead:
+
+ ```php
+ // old ($loop gets passed by default)
+ $loop->addReadStream($stream, function ($stream, $loop) {
+ $loop->removeReadStream($stream);
+ });
+
+ // already supported before: use closure binding as usual
+ $loop->addReadStream($stream, function ($stream) use ($loop) {
+ $loop->removeReadStream($stream);
+ });
+ ```
+
+* BC break: Remove unneeded `LoopInterface::removeStream()` method,
+ use `LoopInterface::removeReadStream()` and `LoopInterface::removeWriteStream()` instead.
+ (#118 by @clue)
+
+ ```php
+ // old
+ $loop->removeStream($stream);
+
+ // already supported before
+ $loop->removeReadStream($stream);
+ $loop->removeWriteStream($stream);
+ ```
+
+* BC break: Rename `LibEventLoop` to `ExtLibeventLoop` and `LibEvLoop` to `ExtLibevLoop`
+ for consistent naming for event loop implementations.
+ (#128 by @clue)
+
+* BC break: Remove optional `EventBaseConfig` argument from `ExtEventLoop`
+ and make its `FEATURE_FDS` enabled by default.
+ (#156 by @WyriHaximus)
+
+* BC break: Mark all classes as final to discourage inheritance.
+ (#131 by @clue)
+
+* Fix: Fix `ExtEventLoop` to keep track of stream resources (refcount)
+ (#123 by @clue)
+
+* Fix: Ensure large timer interval does not overflow on 32bit systems
+ (#132 by @clue)
+
+* Fix: Fix separately removing readable and writable side of stream when closing
+ (#139 by @clue)
+
+* Fix: Properly clean up event watchers for `ext-event` and `ext-libev`
+ (#149 by @clue)
+
+* Fix: Minor code cleanup and remove unneeded references
+ (#145 by @seregazhuk)
+
+* Fix: Discourage outdated `ext-libevent` on PHP 7
+ (#62 by @cboden)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and PHPUnit 5,
+ lock Travis distro so new defaults will not break the build,
+ improve test suite to be less fragile and increase test timeouts,
+ test against PHP 7.2 and reduce fwrite() call length to one chunk.
+ (#106 and #144 by @clue, #120 and #124 by @carusogabriel, #147 by nawarian and #92 by @kelunik)
+
+* A number of changes were originally planned for this release but have been backported
+ to the last `v0.4.3` already: #74, #76, #79, #81 (refs #65, #66, #67), #88 and #93
+
+## 0.4.3 (2017-04-27)
+
+* Bug fix: Bugfix in the usage sample code #57 (@dandelionred)
+* Improvement: Remove branch-alias definition #53 (@WyriHaximus)
+* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd)
+* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder)
+* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley)
+* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue)
+* Improvement: Travis improvements (backported from #74) #75 (@clue)
+* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder)
+* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder)
+* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek)
+* Improvement: Readme cleanup #89 (@jsor)
+* Improvement: Restructure and improve README #90 (@jsor)
+* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor)
+
+## 0.4.2 (2016-03-07)
+
+* Bug fix: No longer error when signals sent to StreamSelectLoop
+* Support HHVM and PHP7 (@ondrejmirtes, @cebe)
+* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades)
+* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino)
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue)
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.4.0 (2014-02-02)
+
+* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc)
+* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc)
+* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc)
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: New method: `EventLoopInterface::nextTick()`
+* BC break: New method: `EventLoopInterface::futureTick()`
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.5 (2016-12-28)
+
+This is a compatibility release that eases upgrading to the v0.4 release branch.
+You should consider upgrading to the v0.4 release branch.
+
+* Feature: Cap min timer interval at 1µs, thus improving compatibility with v0.4
+ (#47 by @clue)
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: No error on removing non-existent streams (@clue)
+* Bug fix: Do not silently remove feof listeners in `LibEvLoop`
+
+## 0.3.0 (2013-04-14)
+
+* BC break: New timers API (@nrk)
+* BC break: Remove check on return value from stream callbacks (@nrk)
+
+## 0.2.7 (2013-01-05)
+
+* Bug fix: Fix libevent timers with PHP 5.3
+* Bug fix: Fix libevent timer cancellation (@nrk)
+
+## 0.2.6 (2012-12-26)
+
+* Bug fix: Plug memory issue in libevent timers (@cameronjacobson)
+* Bug fix: Correctly pause LibEvLoop on stop()
+
+## 0.2.3 (2012-11-14)
+
+* Feature: LibEvLoop, integration of `php-libev`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Version bump
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/assets/php/vendor/react/event-loop/LICENSE b/assets/php/vendor/react/event-loop/LICENSE
new file mode 100644
index 0000000..a808108
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+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.
diff --git a/assets/php/vendor/react/event-loop/README.md b/assets/php/vendor/react/event-loop/README.md
new file mode 100644
index 0000000..207e7f4
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/README.md
@@ -0,0 +1,702 @@
+# EventLoop Component
+
+[](https://travis-ci.org/reactphp/event-loop)
+
+[ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O.
+
+In order for async based libraries to be interoperable, they need to use the
+same event loop. This component provides a common `LoopInterface` that any
+library can target. This allows them to be used in the same loop, with one
+single [`run()`](#run) call that is controlled by the user.
+
+**Table of Contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [Factory](#factory)
+ * [create()](#create)
+ * [Loop implementations](#loop-implementations)
+ * [StreamSelectLoop](#streamselectloop)
+ * [ExtEventLoop](#exteventloop)
+ * [ExtLibeventLoop](#extlibeventloop)
+ * [ExtLibevLoop](#extlibevloop)
+ * [ExtEvLoop](#extevloop)
+ * [LoopInterface](#loopinterface)
+ * [run()](#run)
+ * [stop()](#stop)
+ * [addTimer()](#addtimer)
+ * [addPeriodicTimer()](#addperiodictimer)
+ * [cancelTimer()](#canceltimer)
+ * [futureTick()](#futuretick)
+ * [addSignal()](#addsignal)
+ * [removeSignal()](#removesignal)
+ * [addReadStream()](#addreadstream)
+ * [addWriteStream()](#addwritestream)
+ * [removeReadStream()](#removereadstream)
+ * [removeWriteStream()](#removewritestream)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Quickstart example
+
+Here is an async HTTP server built with just the event loop.
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$server = stream_socket_server('tcp://127.0.0.1:8080');
+stream_set_blocking($server, false);
+
+$loop->addReadStream($server, function ($server) use ($loop) {
+ $conn = stream_socket_accept($server);
+ $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
+ $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) {
+ $written = fwrite($conn, $data);
+ if ($written === strlen($data)) {
+ fclose($conn);
+ $loop->removeWriteStream($conn);
+ } else {
+ $data = substr($data, $written);
+ }
+ });
+});
+
+$loop->addPeriodicTimer(5, function () {
+ $memory = memory_get_usage() / 1024;
+ $formatted = number_format($memory, 3).'K';
+ echo "Current memory usage: {$formatted}\n";
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+## Usage
+
+Typical applications use a single event loop which is created at the beginning
+and run at the end of the program.
+
+```php
+// [1]
+$loop = React\EventLoop\Factory::create();
+
+// [2]
+$loop->addPeriodicTimer(1, function () {
+ echo "Tick\n";
+});
+
+$stream = new React\Stream\ReadableResourceStream(
+ fopen('file.txt', 'r'),
+ $loop
+);
+
+// [3]
+$loop->run();
+```
+
+1. The loop instance is created at the beginning of the program. A convenience
+ factory [`React\EventLoop\Factory::create()`](#create) is provided by this library which
+ picks the best available [loop implementation](#loop-implementations).
+2. The loop instance is used directly or passed to library and application code.
+ In this example, a periodic timer is registered with the event loop which
+ simply outputs `Tick` every second and a
+ [readable stream](https://github.com/reactphp/stream#readableresourcestream)
+ is created by using ReactPHP's
+ [stream component](https://github.com/reactphp/stream) for demonstration
+ purposes.
+3. The loop is run with a single [`$loop->run()`](#run) call at the end of the program.
+
+### Factory
+
+The `Factory` class exists as a convenient way to pick the best available
+[event loop implementation](#loop-implementations).
+
+#### create()
+
+The `create(): LoopInterface` method can be used to create a new event loop
+instance:
+
+```php
+$loop = React\EventLoop\Factory::create();
+```
+
+This method always returns an instance implementing [`LoopInterface`](#loopinterface),
+the actual [event loop implementation](#loop-implementations) is an implementation detail.
+
+This method should usually only be called once at the beginning of the program.
+
+### Loop implementations
+
+In addition to the [`LoopInterface`](#loopinterface), there are a number of
+event loop implementations provided.
+
+All of the event loops support these features:
+
+* File descriptor polling
+* One-off timers
+* Periodic timers
+* Deferred execution on future loop tick
+
+For most consumers of this package, the underlying event loop implementation is
+an implementation detail.
+You should use the [`Factory`](#factory) to automatically create a new instance.
+
+Advanced! If you explicitly need a certain event loop implementation, you can
+manually instantiate one of the following classes.
+Note that you may have to install the required PHP extensions for the respective
+event loop implementation first or they will throw a `BadMethodCallException` on creation.
+
+#### StreamSelectLoop
+
+A `stream_select()` based event loop.
+
+This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php)
+function and is the only implementation which works out of the box with PHP.
+
+This event loop works out of the box on PHP 5.3 through PHP 7+ and HHVM.
+This means that no installation is required and this library works on all
+platforms and supported PHP versions.
+Accordingly, the [`Factory`](#factory) will use this event loop by default if
+you do not install any of the event loop extensions listed below.
+
+Under the hood, it does a simple `select` system call.
+This system call is limited to the maximum file descriptor number of
+`FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
+(`m` being the maximum file descriptor number passed).
+This means that you may run into issues when handling thousands of streams
+concurrently and you may want to look into using one of the alternative
+event loop implementations listed below in this case.
+If your use case is among the many common use cases that involve handling only
+dozens or a few hundred streams at once, then this event loop implementation
+performs really well.
+
+If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
+this event loop implementation requires `ext-pcntl`.
+This extension is only available for Unix-like platforms and does not support
+Windows.
+It is commonly installed as part of many PHP distributions.
+If this extension is missing (or you're running on Windows), signal handling is
+not supported and throws a `BadMethodCallException` instead.
+
+This event loop is known to rely on wall-clock time to schedule future
+timers, because a monotonic time source is not available in PHP by default.
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you schedule a timer to trigger in 30s and then adjust
+your system time forward by 20s, the timer may trigger in 10s.
+See also [`addTimer()`](#addtimer) for more details.
+
+#### ExtEventLoop
+
+An `ext-event` based event loop.
+
+This uses the [`event` PECL extension](https://pecl.php.net/package/event).
+It supports the same backends as libevent.
+
+This loop is known to work with PHP 5.4 through PHP 7+.
+
+#### ExtEvLoop
+
+An `ext-ev` based event loop.
+
+This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev), that
+provides an interface to `libev` library.
+
+This loop is known to work with PHP 5.4 through PHP 7+.
+
+
+#### ExtLibeventLoop
+
+An `ext-libevent` based event loop.
+
+This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent).
+`libevent` itself supports a number of system-specific backends (epoll, kqueue).
+
+This event loop does only work with PHP 5.
+An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
+PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
+To reiterate: Using this event loop on PHP 7 is not recommended.
+Accordingly, the [`Factory`](#factory) will not try to use this event loop on
+PHP 7.
+
+This event loop is known to trigger a readable listener only if
+the stream *becomes* readable (edge-triggered) and may not trigger if the
+stream has already been readable from the beginning.
+This also implies that a stream may not be recognized as readable when data
+is still left in PHP's internal stream buffers.
+As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+to disable PHP's internal read buffer in this case.
+See also [`addReadStream()`](#addreadstream) for more details.
+
+#### ExtLibevLoop
+
+An `ext-libev` based event loop.
+
+This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev).
+It supports the same backends as libevent.
+
+This loop does only work with PHP 5.
+An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
+to happen any time soon.
+
+### LoopInterface
+
+#### run()
+
+The `run(): void` method can be used to
+run the event loop until there are no more tasks to perform.
+
+For many applications, this method is the only directly visible
+invocation on the event loop.
+As a rule of thumb, it is usally recommended to attach everything to the
+same loop instance and then run the loop once at the bottom end of the
+application.
+
+```php
+$loop->run();
+```
+
+This method will keep the loop running until there are no more tasks
+to perform. In other words: This method will block until the last
+timer, stream and/or signal has been removed.
+
+Likewise, it is imperative to ensure the application actually invokes
+this method once. Adding listeners to the loop and missing to actually
+run it will result in the application exiting without actually waiting
+for any of the attached listeners.
+
+This method MUST NOT be called while the loop is already running.
+This method MAY be called more than once after it has explicity been
+[`stop()`ped](#stop) or after it automatically stopped because it
+previously did no longer have anything to do.
+
+#### stop()
+
+The `stop(): void` method can be used to
+instruct a running event loop to stop.
+
+This method is considered advanced usage and should be used with care.
+As a rule of thumb, it is usually recommended to let the loop stop
+only automatically when it no longer has anything to do.
+
+This method can be used to explicitly instruct the event loop to stop:
+
+```php
+$loop->addTimer(3.0, function () use ($loop) {
+ $loop->stop();
+});
+```
+
+Calling this method on a loop instance that is not currently running or
+on a loop instance that has already been stopped has no effect.
+
+#### addTimer()
+
+The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to
+enqueue a callback to be invoked once after the given interval.
+
+The timer callback function MUST be able to accept a single parameter,
+the timer instance as also returned by this method or you MAY use a
+function which has no parameters at all.
+
+The timer callback function MUST NOT throw an `Exception`.
+The return value of the timer callback function will be ignored and has
+no effect, so for performance reasons you're recommended to not return
+any excessive data structures.
+
+Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
+the callback will be invoked only once after the given interval.
+You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
+
+```php
+$loop->addTimer(0.8, function () {
+ echo 'world!' . PHP_EOL;
+});
+
+$loop->addTimer(0.3, function () {
+ echo 'hello ';
+});
+```
+
+See also [example #1](examples).
+
+If you want to access any variables within your callback function, you
+can bind arbitrary data to a callback closure like this:
+
+```php
+function hello($name, LoopInterface $loop)
+{
+ $loop->addTimer(1.0, function () use ($name) {
+ echo "hello $name\n";
+ });
+}
+
+hello('Tester', $loop);
+```
+
+This interface does not enforce any particular timer resolution, so
+special care may have to be taken if you rely on very high precision with
+millisecond accuracy or below. Event loop implementations SHOULD work on
+a best effort basis and SHOULD provide at least millisecond accuracy
+unless otherwise noted. Many existing event loop implementations are
+known to provide microsecond accuracy, but it's generally not recommended
+to rely on this high precision.
+
+Similarly, the execution order of timers scheduled to execute at the
+same time (within its possible accuracy) is not guaranteed.
+
+This interface suggests that event loop implementations SHOULD use a
+monotonic time source if available. Given that a monotonic time source is
+not available on PHP by default, event loop implementations MAY fall back
+to using wall-clock time.
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you schedule a timer to trigger in 30s and then adjust
+your system time forward by 20s, the timer SHOULD still trigger in 30s.
+See also [event loop implementations](#loop-implementations) for more details.
+
+#### addPeriodicTimer()
+
+The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to
+enqueue a callback to be invoked repeatedly after the given interval.
+
+The timer callback function MUST be able to accept a single parameter,
+the timer instance as also returned by this method or you MAY use a
+function which has no parameters at all.
+
+The timer callback function MUST NOT throw an `Exception`.
+The return value of the timer callback function will be ignored and has
+no effect, so for performance reasons you're recommended to not return
+any excessive data structures.
+
+Unlike [`addTimer()`](#addtimer), this method will ensure the the
+callback will be invoked infinitely after the given interval or until you
+invoke [`cancelTimer`](#canceltimer).
+
+```php
+$timer = $loop->addPeriodicTimer(0.1, function () {
+ echo 'tick!' . PHP_EOL;
+});
+
+$loop->addTimer(1.0, function () use ($loop, $timer) {
+ $loop->cancelTimer($timer);
+ echo 'Done' . PHP_EOL;
+});
+```
+
+See also [example #2](examples).
+
+If you want to limit the number of executions, you can bind
+arbitrary data to a callback closure like this:
+
+```php
+function hello($name, LoopInterface $loop)
+{
+ $n = 3;
+ $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
+ if ($n > 0) {
+ --$n;
+ echo "hello $name\n";
+ } else {
+ $loop->cancelTimer($timer);
+ }
+ });
+}
+
+hello('Tester', $loop);
+```
+
+This interface does not enforce any particular timer resolution, so
+special care may have to be taken if you rely on very high precision with
+millisecond accuracy or below. Event loop implementations SHOULD work on
+a best effort basis and SHOULD provide at least millisecond accuracy
+unless otherwise noted. Many existing event loop implementations are
+known to provide microsecond accuracy, but it's generally not recommended
+to rely on this high precision.
+
+Similarly, the execution order of timers scheduled to execute at the
+same time (within its possible accuracy) is not guaranteed.
+
+This interface suggests that event loop implementations SHOULD use a
+monotonic time source if available. Given that a monotonic time source is
+not available on PHP by default, event loop implementations MAY fall back
+to using wall-clock time.
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you schedule a timer to trigger in 30s and then adjust
+your system time forward by 20s, the timer SHOULD still trigger in 30s.
+See also [event loop implementations](#loop-implementations) for more details.
+
+Additionally, periodic timers may be subject to timer drift due to
+re-scheduling after each invocation. As such, it's generally not
+recommended to rely on this for high precision intervals with millisecond
+accuracy or below.
+
+#### cancelTimer()
+
+The `cancelTimer(TimerInterface $timer): void` method can be used to
+cancel a pending timer.
+
+See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
+
+Calling this method on a timer instance that has not been added to this
+loop instance or on a timer that has already been cancelled has no effect.
+
+#### futureTick()
+
+The `futureTick(callable $listener): void` method can be used to
+schedule a callback to be invoked on a future tick of the event loop.
+
+This works very much similar to timers with an interval of zero seconds,
+but does not require the overhead of scheduling a timer queue.
+
+The tick callback function MUST be able to accept zero parameters.
+
+The tick callback function MUST NOT throw an `Exception`.
+The return value of the tick callback function will be ignored and has
+no effect, so for performance reasons you're recommended to not return
+any excessive data structures.
+
+If you want to access any variables within your callback function, you
+can bind arbitrary data to a callback closure like this:
+
+```php
+function hello($name, LoopInterface $loop)
+{
+ $loop->futureTick(function () use ($name) {
+ echo "hello $name\n";
+ });
+}
+
+hello('Tester', $loop);
+```
+
+Unlike timers, tick callbacks are guaranteed to be executed in the order
+they are enqueued.
+Also, once a callback is enqueued, there's no way to cancel this operation.
+
+This is often used to break down bigger tasks into smaller steps (a form
+of cooperative multitasking).
+
+```php
+$loop->futureTick(function () {
+ echo 'b';
+});
+$loop->futureTick(function () {
+ echo 'c';
+});
+echo 'a';
+```
+
+See also [example #3](examples).
+
+#### addSignal()
+
+The `addSignal(int $signal, callable $listener): void` method can be used to
+register a listener to be notified when a signal has been caught by this process.
+
+This is useful to catch user interrupt signals or shutdown signals from
+tools like `supervisor` or `systemd`.
+
+The listener callback function MUST be able to accept a single parameter,
+the signal added by this method or you MAY use a function which
+has no parameters at all.
+
+The listener callback function MUST NOT throw an `Exception`.
+The return value of the listener callback function will be ignored and has
+no effect, so for performance reasons you're recommended to not return
+any excessive data structures.
+
+```php
+$loop->addSignal(SIGINT, function (int $signal) {
+ echo 'Caught user interrupt signal' . PHP_EOL;
+});
+```
+
+See also [example #4](examples).
+
+Signaling is only available on Unix-like platform, Windows isn't
+supported due to operating system limitations.
+This method may throw a `BadMethodCallException` if signals aren't
+supported on this platform, for example when required extensions are
+missing.
+
+**Note: A listener can only be added once to the same signal, any
+attempts to add it more then once will be ignored.**
+
+#### removeSignal()
+
+The `removeSignal(int $signal, callable $listener): void` method can be used to
+remove a previously added signal listener.
+
+```php
+$loop->removeSignal(SIGINT, $listener);
+```
+
+Any attempts to remove listeners that aren't registered will be ignored.
+
+#### addReadStream()
+
+> Advanced! Note that this low-level API is considered advanced usage.
+ Most use cases should probably use the higher-level
+ [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
+ instead.
+
+The `addReadStream(resource $stream, callable $callback): void` method can be used to
+register a listener to be notified when a stream is ready to read.
+
+The first parameter MUST be a valid stream resource that supports
+checking whether it is ready to read by this loop implementation.
+A single stream resource MUST NOT be added more than once.
+Instead, either call [`removeReadStream()`](#removereadstream) first or
+react to this event with a single listener and then dispatch from this
+listener. This method MAY throw an `Exception` if the given resource type
+is not supported by this loop implementation.
+
+The listener callback function MUST be able to accept a single parameter,
+the stream resource added by this method or you MAY use a function which
+has no parameters at all.
+
+The listener callback function MUST NOT throw an `Exception`.
+The return value of the listener callback function will be ignored and has
+no effect, so for performance reasons you're recommended to not return
+any excessive data structures.
+
+If you want to access any variables within your callback function, you
+can bind arbitrary data to a callback closure like this:
+
+```php
+$loop->addReadStream($stream, function ($stream) use ($name) {
+ echo $name . ' said: ' . fread($stream);
+});
+```
+
+See also [example #11](examples).
+
+You can invoke [`removeReadStream()`](#removereadstream) to remove the
+read event listener for this stream.
+
+The execution order of listeners when multiple streams become ready at
+the same time is not guaranteed.
+
+Some event loop implementations are known to only trigger the listener if
+the stream *becomes* readable (edge-triggered) and may not trigger if the
+stream has already been readable from the beginning.
+This also implies that a stream may not be recognized as readable when data
+is still left in PHP's internal stream buffers.
+As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+to disable PHP's internal read buffer in this case.
+
+#### addWriteStream()
+
+> Advanced! Note that this low-level API is considered advanced usage.
+ Most use cases should probably use the higher-level
+ [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
+ instead.
+
+The `addWriteStream(resource $stream, callable $callback): void` method can be used to
+register a listener to be notified when a stream is ready to write.
+
+The first parameter MUST be a valid stream resource that supports
+checking whether it is ready to write by this loop implementation.
+A single stream resource MUST NOT be added more than once.
+Instead, either call [`removeWriteStream()`](#removewritestream) first or
+react to this event with a single listener and then dispatch from this
+listener. This method MAY throw an `Exception` if the given resource type
+is not supported by this loop implementation.
+
+The listener callback function MUST be able to accept a single parameter,
+the stream resource added by this method or you MAY use a function which
+has no parameters at all.
+
+The listener callback function MUST NOT throw an `Exception`.
+The return value of the listener callback function will be ignored and has
+no effect, so for performance reasons you're recommended to not return
+any excessive data structures.
+
+If you want to access any variables within your callback function, you
+can bind arbitrary data to a callback closure like this:
+
+```php
+$loop->addWriteStream($stream, function ($stream) use ($name) {
+ fwrite($stream, 'Hello ' . $name);
+});
+```
+
+See also [example #12](examples).
+
+You can invoke [`removeWriteStream()`](#removewritestream) to remove the
+write event listener for this stream.
+
+The execution order of listeners when multiple streams become ready at
+the same time is not guaranteed.
+
+#### removeReadStream()
+
+The `removeReadStream(resource $stream): void` method can be used to
+remove the read event listener for the given stream.
+
+Removing a stream from the loop that has already been removed or trying
+to remove a stream that was never added or is invalid has no effect.
+
+#### removeWriteStream()
+
+The `removeWriteStream(resource $stream): void` method can be used to
+remove the write event listener for the given stream.
+
+Removing a stream from the loop that has already been removed or trying
+to remove a stream that was never added or is invalid has no effect.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/event-loop:^0.5.1
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+Installing any of the event loop extensions is suggested, but entirely optional.
+See also [event loop implementations](#loop-implementations) for more details.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## More
+
+* See our [Stream component](https://github.com/reactphp/stream) for more
+ information on how streams are used in real-world applications.
+* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
+ [dependents on Packagist](https://packagist.org/packages/react/event-loop/dependents)
+ for a list of packages that use the EventLoop in real-world applications.
diff --git a/assets/php/vendor/react/event-loop/composer.json b/assets/php/vendor/react/event-loop/composer.json
new file mode 100644
index 0000000..24974ec
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/composer.json
@@ -0,0 +1,21 @@
+{
+ "name": "react/event-loop",
+ "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+ "keywords": ["event-loop", "asynchronous"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4"
+ },
+ "suggest": {
+ "ext-event": "~1.0 for ExtEventLoop",
+ "ext-pcntl": "For signal handling support when using the StreamSelectLoop"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src"
+ }
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/examples/01-timers.php b/assets/php/vendor/react/event-loop/examples/01-timers.php
new file mode 100644
index 0000000..e6107e4
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/01-timers.php
@@ -0,0 +1,15 @@
+addTimer(0.8, function () {
+ echo 'world!' . PHP_EOL;
+});
+
+$loop->addTimer(0.3, function () {
+ echo 'hello ';
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/02-periodic.php b/assets/php/vendor/react/event-loop/examples/02-periodic.php
new file mode 100644
index 0000000..5e138a6
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/02-periodic.php
@@ -0,0 +1,16 @@
+addPeriodicTimer(0.1, function () {
+ echo 'tick!' . PHP_EOL;
+});
+
+$loop->addTimer(1.0, function () use ($loop, $timer) {
+ $loop->cancelTimer($timer);
+ echo 'Done' . PHP_EOL;
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/03-ticks.php b/assets/php/vendor/react/event-loop/examples/03-ticks.php
new file mode 100644
index 0000000..3f36c6d
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/03-ticks.php
@@ -0,0 +1,15 @@
+futureTick(function () {
+ echo 'b';
+});
+$loop->futureTick(function () {
+ echo 'c';
+});
+echo 'a';
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/04-signals.php b/assets/php/vendor/react/event-loop/examples/04-signals.php
new file mode 100644
index 0000000..90b6898
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/04-signals.php
@@ -0,0 +1,19 @@
+addSignal(SIGINT, $func = function ($signal) use ($loop, &$func) {
+ echo 'Signal: ', (string)$signal, PHP_EOL;
+ $loop->removeSignal(SIGINT, $func);
+});
+
+echo 'Listening for SIGINT. Use "kill -SIGINT ' . getmypid() . '" or CTRL+C' . PHP_EOL;
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/11-consume-stdin.php b/assets/php/vendor/react/event-loop/examples/11-consume-stdin.php
new file mode 100644
index 0000000..2a77245
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/11-consume-stdin.php
@@ -0,0 +1,30 @@
+addReadStream(STDIN, function ($stream) use ($loop) {
+ $chunk = fread($stream, 64 * 1024);
+
+ // reading nothing means we reached EOF
+ if ($chunk === '') {
+ $loop->removeReadStream($stream);
+ stream_set_blocking($stream, true);
+ fclose($stream);
+ return;
+ }
+
+ echo strlen($chunk) . ' bytes' . PHP_EOL;
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/12-generate-yes.php b/assets/php/vendor/react/event-loop/examples/12-generate-yes.php
new file mode 100644
index 0000000..ebc2beb
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/12-generate-yes.php
@@ -0,0 +1,41 @@
+addWriteStream(STDOUT, function ($stdout) use ($loop, &$data) {
+ // try to write data
+ $r = fwrite($stdout, $data);
+
+ // nothing could be written despite being writable => closed
+ if ($r === 0) {
+ $loop->removeWriteStream($stdout);
+ fclose($stdout);
+ stream_set_blocking($stdout, true);
+ fwrite(STDERR, 'Stopped because STDOUT closed' . PHP_EOL);
+
+ return;
+ }
+
+ // implement a very simple ring buffer, unless everything has been written at once:
+ // everything written in this iteration will be appended for next iteration
+ if (isset($data[$r])) {
+ $data = substr($data, $r) . substr($data, 0, $r);
+ }
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/13-http-client-blocking.php b/assets/php/vendor/react/event-loop/examples/13-http-client-blocking.php
new file mode 100644
index 0000000..a2dde55
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/13-http-client-blocking.php
@@ -0,0 +1,35 @@
+addReadStream($stream, function ($stream) use ($loop) {
+ $chunk = fread($stream, 64 * 1024);
+
+ // reading nothing means we reached EOF
+ if ($chunk === '') {
+ echo '[END]' . PHP_EOL;
+ $loop->removeReadStream($stream);
+ fclose($stream);
+ return;
+ }
+
+ echo $chunk;
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/14-http-client-async.php b/assets/php/vendor/react/event-loop/examples/14-http-client-async.php
new file mode 100644
index 0000000..c82c988
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/14-http-client-async.php
@@ -0,0 +1,63 @@
+addPeriodicTimer(0.01, function () {
+ echo '.';
+});
+
+// wait for connection success/error
+$loop->addWriteStream($stream, function ($stream) use ($loop, $timer) {
+ $loop->removeWriteStream($stream);
+ $loop->cancelTimer($timer);
+
+ // check for socket error (connection rejected)
+ if (stream_socket_get_name($stream, true) === false) {
+ echo '[unable to connect]' . PHP_EOL;
+ exit(1);
+ } else {
+ echo '[connected]' . PHP_EOL;
+ }
+
+ // send HTTP request
+ fwrite($stream, "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n");
+
+ // wait for HTTP response
+ $loop->addReadStream($stream, function ($stream) use ($loop) {
+ $chunk = fread($stream, 64 * 1024);
+
+ // reading nothing means we reached EOF
+ if ($chunk === '') {
+ echo '[END]' . PHP_EOL;
+ $loop->removeReadStream($stream);
+ fclose($stream);
+ return;
+ }
+
+ echo $chunk;
+ });
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/21-http-server.php b/assets/php/vendor/react/event-loop/examples/21-http-server.php
new file mode 100644
index 0000000..89520ce
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/21-http-server.php
@@ -0,0 +1,36 @@
+addReadStream($server, function ($server) use ($loop) {
+ $conn = stream_socket_accept($server);
+ $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
+ $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) {
+ $written = fwrite($conn, $data);
+ if ($written === strlen($data)) {
+ fclose($conn);
+ $loop->removeWriteStream($conn);
+ } else {
+ $data = substr($data, $written);
+ }
+ });
+});
+
+$loop->addPeriodicTimer(5, function () {
+ $memory = memory_get_usage() / 1024;
+ $formatted = number_format($memory, 3).'K';
+ echo "Current memory usage: {$formatted}\n";
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/91-benchmark-ticks.php b/assets/php/vendor/react/event-loop/examples/91-benchmark-ticks.php
new file mode 100644
index 0000000..3f4690b
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/91-benchmark-ticks.php
@@ -0,0 +1,15 @@
+futureTick(function () { });
+}
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/92-benchmark-timers.php b/assets/php/vendor/react/event-loop/examples/92-benchmark-timers.php
new file mode 100644
index 0000000..e2e02e4
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/92-benchmark-timers.php
@@ -0,0 +1,15 @@
+addTimer(0, function () { });
+}
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/93-benchmark-ticks-delay.php b/assets/php/vendor/react/event-loop/examples/93-benchmark-ticks-delay.php
new file mode 100644
index 0000000..95ee78c
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/93-benchmark-ticks-delay.php
@@ -0,0 +1,22 @@
+ 0) {
+ --$ticks;
+ //$loop->addTimer(0, $tick);
+ $loop->futureTick($tick);
+ } else {
+ echo 'done';
+ }
+};
+
+$tick();
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/94-benchmark-timers-delay.php b/assets/php/vendor/react/event-loop/examples/94-benchmark-timers-delay.php
new file mode 100644
index 0000000..2d6cfa2
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/94-benchmark-timers-delay.php
@@ -0,0 +1,22 @@
+ 0) {
+ --$ticks;
+ //$loop->futureTick($tick);
+ $loop->addTimer(0, $tick);
+ } else {
+ echo 'done';
+ }
+};
+
+$tick();
+
+$loop->run();
diff --git a/assets/php/vendor/react/event-loop/examples/95-benchmark-memory.php b/assets/php/vendor/react/event-loop/examples/95-benchmark-memory.php
new file mode 100644
index 0000000..084c404
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/examples/95-benchmark-memory.php
@@ -0,0 +1,67 @@
+addTimer($t, function () use ($loop) {
+ $loop->stop();
+ });
+
+}
+
+$loop->addPeriodicTimer(0.001, function () use (&$runs, $loop) {
+ $runs++;
+
+ $loop->addPeriodicTimer(1, function (TimerInterface $timer) use ($loop) {
+ $loop->cancelTimer($timer);
+ });
+});
+
+$loop->addPeriodicTimer($r, function () use (&$runs) {
+ $kmem = round(memory_get_usage() / 1024);
+ $kmemReal = round(memory_get_usage(true) / 1024);
+ echo "Runs:\t\t\t$runs\n";
+ echo "Memory (internal):\t$kmem KiB\n";
+ echo "Memory (real):\t\t$kmemReal KiB\n";
+ echo str_repeat('-', 50), "\n";
+});
+
+echo "PHP Version:\t\t", phpversion(), "\n";
+echo "Loop\t\t\t", get_class($loop), "\n";
+echo "Time\t\t\t", date('r'), "\n";
+
+echo str_repeat('-', 50), "\n";
+
+$beginTime = time();
+$loop->run();
+$endTime = time();
+$timeTaken = $endTime - $beginTime;
+
+echo "PHP Version:\t\t", phpversion(), "\n";
+echo "Loop\t\t\t", get_class($loop), "\n";
+echo "Time\t\t\t", date('r'), "\n";
+echo "Time taken\t\t", $timeTaken, " seconds\n";
+echo "Runs per second\t\t", round($runs / $timeTaken), "\n";
diff --git a/assets/php/vendor/react/event-loop/phpunit.xml.dist b/assets/php/vendor/react/event-loop/phpunit.xml.dist
new file mode 100644
index 0000000..cba6d4d
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/assets/php/vendor/react/event-loop/src/ExtEvLoop.php b/assets/php/vendor/react/event-loop/src/ExtEvLoop.php
new file mode 100644
index 0000000..74db6d0
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/ExtEvLoop.php
@@ -0,0 +1,252 @@
+loop = new EvLoop();
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timers = new SplObjectStorage();
+ $this->signals = new SignalsHandler();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ $key = (int)$stream;
+
+ if (isset($this->readStreams[$key])) {
+ return;
+ }
+
+ $callback = $this->getStreamListenerClosure($stream, $listener);
+ $event = $this->loop->io($stream, Ev::READ, $callback);
+ $this->readStreams[$key] = $event;
+ }
+
+ /**
+ * @param resource $stream
+ * @param callable $listener
+ *
+ * @return \Closure
+ */
+ private function getStreamListenerClosure($stream, $listener)
+ {
+ return function () use ($stream, $listener) {
+ call_user_func($listener, $stream);
+ };
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ $key = (int)$stream;
+
+ if (isset($this->writeStreams[$key])) {
+ return;
+ }
+
+ $callback = $this->getStreamListenerClosure($stream, $listener);
+ $event = $this->loop->io($stream, Ev::WRITE, $callback);
+ $this->writeStreams[$key] = $event;
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int)$stream;
+
+ if (!isset($this->readStreams[$key])) {
+ return;
+ }
+
+ $this->readStreams[$key]->stop();
+ unset($this->readStreams[$key]);
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int)$stream;
+
+ if (!isset($this->writeStreams[$key])) {
+ return;
+ }
+
+ $this->writeStreams[$key]->stop();
+ unset($this->writeStreams[$key]);
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $that = $this;
+ $timers = $this->timers;
+ $callback = function () use ($timer, $timers, $that) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($timers->contains($timer)) {
+ $that->cancelTimer($timer);
+ }
+ };
+
+ $event = $this->loop->timer($timer->getInterval(), 0.0, $callback);
+ $this->timers->attach($timer, $event);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $callback = function () use ($timer) {
+ call_user_func($timer->getCallback(), $timer);
+ };
+
+ $event = $this->loop->timer($interval, $interval, $callback);
+ $this->timers->attach($timer, $event);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if (!isset($this->timers[$timer])) {
+ return;
+ }
+
+ $event = $this->timers[$timer];
+ $event->stop();
+ $this->timers->detach($timer);
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
+ $wasJustStopped = !$this->running;
+ $nothingLeftToDo = !$this->readStreams
+ && !$this->writeStreams
+ && !$this->timers->count()
+ && $this->signals->isEmpty();
+
+ $flags = Ev::RUN_ONCE;
+ if ($wasJustStopped || $hasPendingCallbacks) {
+ $flags |= Ev::RUN_NOWAIT;
+ } elseif ($nothingLeftToDo) {
+ break;
+ }
+
+ $this->loop->run($flags);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ public function __destruct()
+ {
+ /** @var TimerInterface $timer */
+ foreach ($this->timers as $timer) {
+ $this->cancelTimer($timer);
+ }
+
+ foreach ($this->readStreams as $key => $stream) {
+ $this->removeReadStream($key);
+ }
+
+ foreach ($this->writeStreams as $key => $stream) {
+ $this->removeWriteStream($key);
+ }
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) {
+ $this->signals->call($signal);
+ });
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal])) {
+ $this->signalEvents[$signal]->stop();
+ unset($this->signalEvents[$signal]);
+ }
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/ExtEventLoop.php b/assets/php/vendor/react/event-loop/src/ExtEventLoop.php
new file mode 100644
index 0000000..622dd47
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/ExtEventLoop.php
@@ -0,0 +1,259 @@
+requireFeatures(EventBaseConfig::FEATURE_FDS);
+
+ $this->eventBase = new EventBase($config);
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timerEvents = new SplObjectStorage();
+ $this->signals = new SignalsHandler();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ $key = (int) $stream;
+ if (isset($this->readListeners[$key])) {
+ return;
+ }
+
+ $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback);
+ $event->add();
+ $this->readEvents[$key] = $event;
+ $this->readListeners[$key] = $listener;
+
+ // ext-event does not increase refcount on stream resources for PHP 7+
+ // manually keep track of stream resource to prevent premature garbage collection
+ if (PHP_VERSION_ID >= 70000) {
+ $this->readRefs[$key] = $stream;
+ }
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ $key = (int) $stream;
+ if (isset($this->writeListeners[$key])) {
+ return;
+ }
+
+ $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback);
+ $event->add();
+ $this->writeEvents[$key] = $event;
+ $this->writeListeners[$key] = $listener;
+
+ // ext-event does not increase refcount on stream resources for PHP 7+
+ // manually keep track of stream resource to prevent premature garbage collection
+ if (PHP_VERSION_ID >= 70000) {
+ $this->writeRefs[$key] = $stream;
+ }
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readEvents[$key])) {
+ $this->readEvents[$key]->free();
+ unset(
+ $this->readEvents[$key],
+ $this->readListeners[$key],
+ $this->readRefs[$key]
+ );
+ }
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeEvents[$key])) {
+ $this->writeEvents[$key]->free();
+ unset(
+ $this->writeEvents[$key],
+ $this->writeListeners[$key],
+ $this->writeRefs[$key]
+ );
+ }
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->timerEvents->contains($timer)) {
+ $this->timerEvents[$timer]->free();
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call'));
+ $this->signalEvents[$signal]->add();
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+ $this->signalEvents[$signal]->free();
+ unset($this->signalEvents[$signal]);
+ }
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $flags = EventBase::LOOP_ONCE;
+ if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventBase::LOOP_NONBLOCK;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+ break;
+ }
+
+ $this->eventBase->loop($flags);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $flags = Event::TIMEOUT;
+
+ if ($timer->isPeriodic()) {
+ $flags |= Event::PERSIST;
+ }
+
+ $event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
+ $this->timerEvents[$timer] = $event;
+
+ $event->add($timer->getInterval());
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $timers = $this->timerEvents;
+ $this->timerCallback = function ($_, $__, $timer) use ($timers) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if (!$timer->isPeriodic() && $timers->contains($timer)) {
+ $this->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $read =& $this->readListeners;
+ $write =& $this->writeListeners;
+ $this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
+ $key = (int) $stream;
+
+ if (Event::READ === (Event::READ & $flags) && isset($read[$key])) {
+ call_user_func($read[$key], $stream);
+ }
+
+ if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) {
+ call_user_func($write[$key], $stream);
+ }
+ };
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/ExtLibevLoop.php b/assets/php/vendor/react/event-loop/src/ExtLibevLoop.php
new file mode 100644
index 0000000..d3b0df8
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/ExtLibevLoop.php
@@ -0,0 +1,199 @@
+loop = new EventLoop();
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timerEvents = new SplObjectStorage();
+ $this->signals = new SignalsHandler();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ if (isset($this->readEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ call_user_func($listener, $stream);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::READ);
+ $this->loop->add($event);
+
+ $this->readEvents[(int) $stream] = $event;
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ if (isset($this->writeEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ call_user_func($listener, $stream);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::WRITE);
+ $this->loop->add($event);
+
+ $this->writeEvents[(int) $stream] = $event;
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readEvents[$key])) {
+ $this->readEvents[$key]->stop();
+ $this->loop->remove($this->readEvents[$key]);
+ unset($this->readEvents[$key]);
+ }
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeEvents[$key])) {
+ $this->writeEvents[$key]->stop();
+ $this->loop->remove($this->writeEvents[$key]);
+ unset($this->writeEvents[$key]);
+ }
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer( $interval, $callback, false);
+
+ $that = $this;
+ $timers = $this->timerEvents;
+ $callback = function () use ($timer, $timers, $that) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($timers->contains($timer)) {
+ $that->cancelTimer($timer);
+ }
+ };
+
+ $event = new TimerEvent($callback, $timer->getInterval());
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $callback = function () use ($timer) {
+ call_user_func($timer->getCallback(), $timer);
+ };
+
+ $event = new TimerEvent($callback, $interval, $interval);
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if (isset($this->timerEvents[$timer])) {
+ $this->loop->remove($this->timerEvents[$timer]);
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $signals = $this->signals;
+ $this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) {
+ $signals->call($signal);
+ }, $signal);
+ $this->loop->add($this->signalEvents[$signal]);
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+ $this->signalEvents[$signal]->stop();
+ $this->loop->remove($this->signalEvents[$signal]);
+ unset($this->signalEvents[$signal]);
+ }
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $flags = EventLoop::RUN_ONCE;
+ if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventLoop::RUN_NOWAIT;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+ break;
+ }
+
+ $this->loop->run($flags);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/ExtLibeventLoop.php b/assets/php/vendor/react/event-loop/src/ExtLibeventLoop.php
new file mode 100644
index 0000000..427f8db
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/ExtLibeventLoop.php
@@ -0,0 +1,283 @@
+eventBase = event_base_new();
+ $this->futureTickQueue = new FutureTickQueue();
+ $this->timerEvents = new SplObjectStorage();
+ $this->signals = new SignalsHandler();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ $key = (int) $stream;
+ if (isset($this->readListeners[$key])) {
+ return;
+ }
+
+ $event = event_new();
+ event_set($event, $stream, EV_PERSIST | EV_READ, $this->streamCallback);
+ event_base_set($event, $this->eventBase);
+ event_add($event);
+
+ $this->readEvents[$key] = $event;
+ $this->readListeners[$key] = $listener;
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ $key = (int) $stream;
+ if (isset($this->writeListeners[$key])) {
+ return;
+ }
+
+ $event = event_new();
+ event_set($event, $stream, EV_PERSIST | EV_WRITE, $this->streamCallback);
+ event_base_set($event, $this->eventBase);
+ event_add($event);
+
+ $this->writeEvents[$key] = $event;
+ $this->writeListeners[$key] = $listener;
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ $event = $this->readEvents[$key];
+ event_del($event);
+ event_free($event);
+
+ unset(
+ $this->readEvents[$key],
+ $this->readListeners[$key]
+ );
+ }
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ $event = $this->writeEvents[$key];
+ event_del($event);
+ event_free($event);
+
+ unset(
+ $this->writeEvents[$key],
+ $this->writeListeners[$key]
+ );
+ }
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->timerEvents->contains($timer)) {
+ $event = $this->timerEvents[$timer];
+ event_del($event);
+ event_free($event);
+
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ $this->signals->add($signal, $listener);
+
+ if (!isset($this->signalEvents[$signal])) {
+ $this->signalEvents[$signal] = event_new();
+ event_set($this->signalEvents[$signal], $signal, EV_PERSIST | EV_SIGNAL, array($this->signals, 'call'));
+ event_base_set($this->signalEvents[$signal], $this->eventBase);
+ event_add($this->signalEvents[$signal]);
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ $this->signals->remove($signal, $listener);
+
+ if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+ event_del($this->signalEvents[$signal]);
+ event_free($this->signalEvents[$signal]);
+ unset($this->signalEvents[$signal]);
+ }
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $flags = EVLOOP_ONCE;
+ if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EVLOOP_NONBLOCK;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+ break;
+ }
+
+ event_base_loop($this->eventBase, $flags);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $this->timerEvents[$timer] = $event = event_timer_new();
+
+ event_timer_set($event, $this->timerCallback, $timer);
+ event_base_set($event, $this->eventBase);
+ event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $that = $this;
+ $timers = $this->timerEvents;
+ $this->timerCallback = function ($_, $__, $timer) use ($timers, $that) {
+ call_user_func($timer->getCallback(), $timer);
+
+ // Timer already cancelled ...
+ if (!$timers->contains($timer)) {
+ return;
+ }
+
+ // Reschedule periodic timers ...
+ if ($timer->isPeriodic()) {
+ event_add(
+ $timers[$timer],
+ $timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND
+ );
+
+ // Clean-up one shot timers ...
+ } else {
+ $that->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $read =& $this->readListeners;
+ $write =& $this->writeListeners;
+ $this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
+ $key = (int) $stream;
+
+ if (EV_READ === (EV_READ & $flags) && isset($read[$key])) {
+ call_user_func($read[$key], $stream);
+ }
+
+ if (EV_WRITE === (EV_WRITE & $flags) && isset($write[$key])) {
+ call_user_func($write[$key], $stream);
+ }
+ };
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/Factory.php b/assets/php/vendor/react/event-loop/src/Factory.php
new file mode 100644
index 0000000..b46fc07
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/Factory.php
@@ -0,0 +1,41 @@
+addReadStream($stream, function ($stream) use ($name) {
+ * echo $name . ' said: ' . fread($stream);
+ * });
+ * ```
+ *
+ * See also [example #11](examples).
+ *
+ * You can invoke [`removeReadStream()`](#removereadstream) to remove the
+ * read event listener for this stream.
+ *
+ * The execution order of listeners when multiple streams become ready at
+ * the same time is not guaranteed.
+ *
+ * @param resource $stream The PHP stream resource to check.
+ * @param callable $listener Invoked when the stream is ready.
+ * @throws \Exception if the given resource type is not supported by this loop implementation
+ * @see self::removeReadStream()
+ */
+ public function addReadStream($stream, $listener);
+
+ /**
+ * [Advanced] Register a listener to be notified when a stream is ready to write.
+ *
+ * Note that this low-level API is considered advanced usage.
+ * Most use cases should probably use the higher-level
+ * [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
+ * instead.
+ *
+ * The first parameter MUST be a valid stream resource that supports
+ * checking whether it is ready to write by this loop implementation.
+ * A single stream resource MUST NOT be added more than once.
+ * Instead, either call [`removeWriteStream()`](#removewritestream) first or
+ * react to this event with a single listener and then dispatch from this
+ * listener. This method MAY throw an `Exception` if the given resource type
+ * is not supported by this loop implementation.
+ *
+ * The listener callback function MUST be able to accept a single parameter,
+ * the stream resource added by this method or you MAY use a function which
+ * has no parameters at all.
+ *
+ * The listener callback function MUST NOT throw an `Exception`.
+ * The return value of the listener callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * If you want to access any variables within your callback function, you
+ * can bind arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * $loop->addWriteStream($stream, function ($stream) use ($name) {
+ * fwrite($stream, 'Hello ' . $name);
+ * });
+ * ```
+ *
+ * See also [example #12](examples).
+ *
+ * You can invoke [`removeWriteStream()`](#removewritestream) to remove the
+ * write event listener for this stream.
+ *
+ * The execution order of listeners when multiple streams become ready at
+ * the same time is not guaranteed.
+ *
+ * Some event loop implementations are known to only trigger the listener if
+ * the stream *becomes* readable (edge-triggered) and may not trigger if the
+ * stream has already been readable from the beginning.
+ * This also implies that a stream may not be recognized as readable when data
+ * is still left in PHP's internal stream buffers.
+ * As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+ * to disable PHP's internal read buffer in this case.
+ *
+ * @param resource $stream The PHP stream resource to check.
+ * @param callable $listener Invoked when the stream is ready.
+ * @throws \Exception if the given resource type is not supported by this loop implementation
+ * @see self::removeWriteStream()
+ */
+ public function addWriteStream($stream, $listener);
+
+ /**
+ * Remove the read event listener for the given stream.
+ *
+ * Removing a stream from the loop that has already been removed or trying
+ * to remove a stream that was never added or is invalid has no effect.
+ *
+ * @param resource $stream The PHP stream resource.
+ */
+ public function removeReadStream($stream);
+
+ /**
+ * Remove the write event listener for the given stream.
+ *
+ * Removing a stream from the loop that has already been removed or trying
+ * to remove a stream that was never added or is invalid has no effect.
+ *
+ * @param resource $stream The PHP stream resource.
+ */
+ public function removeWriteStream($stream);
+
+ /**
+ * Enqueue a callback to be invoked once after the given interval.
+ *
+ * The timer callback function MUST be able to accept a single parameter,
+ * the timer instance as also returned by this method or you MAY use a
+ * function which has no parameters at all.
+ *
+ * The timer callback function MUST NOT throw an `Exception`.
+ * The return value of the timer callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
+ * the callback will be invoked only once after the given interval.
+ * You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
+ *
+ * ```php
+ * $loop->addTimer(0.8, function () {
+ * echo 'world!' . PHP_EOL;
+ * });
+ *
+ * $loop->addTimer(0.3, function () {
+ * echo 'hello ';
+ * });
+ * ```
+ *
+ * See also [example #1](examples).
+ *
+ * If you want to access any variables within your callback function, you
+ * can bind arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * function hello($name, LoopInterface $loop)
+ * {
+ * $loop->addTimer(1.0, function () use ($name) {
+ * echo "hello $name\n";
+ * });
+ * }
+ *
+ * hello('Tester', $loop);
+ * ```
+ *
+ * This interface does not enforce any particular timer resolution, so
+ * special care may have to be taken if you rely on very high precision with
+ * millisecond accuracy or below. Event loop implementations SHOULD work on
+ * a best effort basis and SHOULD provide at least millisecond accuracy
+ * unless otherwise noted. Many existing event loop implementations are
+ * known to provide microsecond accuracy, but it's generally not recommended
+ * to rely on this high precision.
+ *
+ * Similarly, the execution order of timers scheduled to execute at the
+ * same time (within its possible accuracy) is not guaranteed.
+ *
+ * This interface suggests that event loop implementations SHOULD use a
+ * monotonic time source if available. Given that a monotonic time source is
+ * not available on PHP by default, event loop implementations MAY fall back
+ * to using wall-clock time.
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you schedule a timer to trigger in 30s and then adjust
+ * your system time forward by 20s, the timer SHOULD still trigger in 30s.
+ * See also [event loop implementations](#loop-implementations) for more details.
+ *
+ * @param int|float $interval The number of seconds to wait before execution.
+ * @param callable $callback The callback to invoke.
+ *
+ * @return TimerInterface
+ */
+ public function addTimer($interval, $callback);
+
+ /**
+ * Enqueue a callback to be invoked repeatedly after the given interval.
+ *
+ * The timer callback function MUST be able to accept a single parameter,
+ * the timer instance as also returned by this method or you MAY use a
+ * function which has no parameters at all.
+ *
+ * The timer callback function MUST NOT throw an `Exception`.
+ * The return value of the timer callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * Unlike [`addTimer()`](#addtimer), this method will ensure the the
+ * callback will be invoked infinitely after the given interval or until you
+ * invoke [`cancelTimer`](#canceltimer).
+ *
+ * ```php
+ * $timer = $loop->addPeriodicTimer(0.1, function () {
+ * echo 'tick!' . PHP_EOL;
+ * });
+ *
+ * $loop->addTimer(1.0, function () use ($loop, $timer) {
+ * $loop->cancelTimer($timer);
+ * echo 'Done' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also [example #2](examples).
+ *
+ * If you want to limit the number of executions, you can bind
+ * arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * function hello($name, LoopInterface $loop)
+ * {
+ * $n = 3;
+ * $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
+ * if ($n > 0) {
+ * --$n;
+ * echo "hello $name\n";
+ * } else {
+ * $loop->cancelTimer($timer);
+ * }
+ * });
+ * }
+ *
+ * hello('Tester', $loop);
+ * ```
+ *
+ * This interface does not enforce any particular timer resolution, so
+ * special care may have to be taken if you rely on very high precision with
+ * millisecond accuracy or below. Event loop implementations SHOULD work on
+ * a best effort basis and SHOULD provide at least millisecond accuracy
+ * unless otherwise noted. Many existing event loop implementations are
+ * known to provide microsecond accuracy, but it's generally not recommended
+ * to rely on this high precision.
+ *
+ * Similarly, the execution order of timers scheduled to execute at the
+ * same time (within its possible accuracy) is not guaranteed.
+ *
+ * This interface suggests that event loop implementations SHOULD use a
+ * monotonic time source if available. Given that a monotonic time source is
+ * not available on PHP by default, event loop implementations MAY fall back
+ * to using wall-clock time.
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you schedule a timer to trigger in 30s and then adjust
+ * your system time forward by 20s, the timer SHOULD still trigger in 30s.
+ * See also [event loop implementations](#loop-implementations) for more details.
+ *
+ * Additionally, periodic timers may be subject to timer drift due to
+ * re-scheduling after each invocation. As such, it's generally not
+ * recommended to rely on this for high precision intervals with millisecond
+ * accuracy or below.
+ *
+ * @param int|float $interval The number of seconds to wait before execution.
+ * @param callable $callback The callback to invoke.
+ *
+ * @return TimerInterface
+ */
+ public function addPeriodicTimer($interval, $callback);
+
+ /**
+ * Cancel a pending timer.
+ *
+ * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
+ *
+ * Calling this method on a timer instance that has not been added to this
+ * loop instance or on a timer that has already been cancelled has no effect.
+ *
+ * @param TimerInterface $timer The timer to cancel.
+ *
+ * @return void
+ */
+ public function cancelTimer(TimerInterface $timer);
+
+ /**
+ * Schedule a callback to be invoked on a future tick of the event loop.
+ *
+ * This works very much similar to timers with an interval of zero seconds,
+ * but does not require the overhead of scheduling a timer queue.
+ *
+ * The tick callback function MUST be able to accept zero parameters.
+ *
+ * The tick callback function MUST NOT throw an `Exception`.
+ * The return value of the tick callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * If you want to access any variables within your callback function, you
+ * can bind arbitrary data to a callback closure like this:
+ *
+ * ```php
+ * function hello($name, LoopInterface $loop)
+ * {
+ * $loop->futureTick(function () use ($name) {
+ * echo "hello $name\n";
+ * });
+ * }
+ *
+ * hello('Tester', $loop);
+ * ```
+ *
+ * Unlike timers, tick callbacks are guaranteed to be executed in the order
+ * they are enqueued.
+ * Also, once a callback is enqueued, there's no way to cancel this operation.
+ *
+ * This is often used to break down bigger tasks into smaller steps (a form
+ * of cooperative multitasking).
+ *
+ * ```php
+ * $loop->futureTick(function () {
+ * echo 'b';
+ * });
+ * $loop->futureTick(function () {
+ * echo 'c';
+ * });
+ * echo 'a';
+ * ```
+ *
+ * See also [example #3](examples).
+ *
+ * @param callable $listener The callback to invoke.
+ *
+ * @return void
+ */
+ public function futureTick($listener);
+
+ /**
+ * Register a listener to be notified when a signal has been caught by this process.
+ *
+ * This is useful to catch user interrupt signals or shutdown signals from
+ * tools like `supervisor` or `systemd`.
+ *
+ * The listener callback function MUST be able to accept a single parameter,
+ * the signal added by this method or you MAY use a function which
+ * has no parameters at all.
+ *
+ * The listener callback function MUST NOT throw an `Exception`.
+ * The return value of the listener callback function will be ignored and has
+ * no effect, so for performance reasons you're recommended to not return
+ * any excessive data structures.
+ *
+ * ```php
+ * $loop->addSignal(SIGINT, function (int $signal) {
+ * echo 'Caught user interrupt signal' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also [example #4](examples).
+ *
+ * Signaling is only available on Unix-like platform, Windows isn't
+ * supported due to operating system limitations.
+ * This method may throw a `BadMethodCallException` if signals aren't
+ * supported on this platform, for example when required extensions are
+ * missing.
+ *
+ * **Note: A listener can only be added once to the same signal, any
+ * attempts to add it more then once will be ignored.**
+ *
+ * @param int $signal
+ * @param callable $listener
+ *
+ * @throws \BadMethodCallException when signals aren't supported on this
+ * platform, for example when required extensions are missing.
+ *
+ * @return void
+ */
+ public function addSignal($signal, $listener);
+
+ /**
+ * Removes a previously added signal listener.
+ *
+ * ```php
+ * $loop->removeSignal(SIGINT, $listener);
+ * ```
+ *
+ * Any attempts to remove listeners that aren't registered will be ignored.
+ *
+ * @param int $signal
+ * @param callable $listener
+ *
+ * @return void
+ */
+ public function removeSignal($signal, $listener);
+
+ /**
+ * Run the event loop until there are no more tasks to perform.
+ *
+ * For many applications, this method is the only directly visible
+ * invocation on the event loop.
+ * As a rule of thumb, it is usally recommended to attach everything to the
+ * same loop instance and then run the loop once at the bottom end of the
+ * application.
+ *
+ * ```php
+ * $loop->run();
+ * ```
+ *
+ * This method will keep the loop running until there are no more tasks
+ * to perform. In other words: This method will block until the last
+ * timer, stream and/or signal has been removed.
+ *
+ * Likewise, it is imperative to ensure the application actually invokes
+ * this method once. Adding listeners to the loop and missing to actually
+ * run it will result in the application exiting without actually waiting
+ * for any of the attached listeners.
+ *
+ * This method MUST NOT be called while the loop is already running.
+ * This method MAY be called more than once after it has explicity been
+ * [`stop()`ped](#stop) or after it automatically stopped because it
+ * previously did no longer have anything to do.
+ *
+ * @return void
+ */
+ public function run();
+
+ /**
+ * Instruct a running event loop to stop.
+ *
+ * This method is considered advanced usage and should be used with care.
+ * As a rule of thumb, it is usually recommended to let the loop stop
+ * only automatically when it no longer has anything to do.
+ *
+ * This method can be used to explicitly instruct the event loop to stop:
+ *
+ * ```php
+ * $loop->addTimer(3.0, function () use ($loop) {
+ * $loop->stop();
+ * });
+ * ```
+ *
+ * Calling this method on a loop instance that is not currently running or
+ * on a loop instance that has already been stopped has no effect.
+ *
+ * @return void
+ */
+ public function stop();
+}
diff --git a/assets/php/vendor/react/event-loop/src/SignalsHandler.php b/assets/php/vendor/react/event-loop/src/SignalsHandler.php
new file mode 100644
index 0000000..523e1ca
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/SignalsHandler.php
@@ -0,0 +1,63 @@
+signals[$signal])) {
+ $this->signals[$signal] = array();
+ }
+
+ if (in_array($listener, $this->signals[$signal])) {
+ return;
+ }
+
+ $this->signals[$signal][] = $listener;
+ }
+
+ public function remove($signal, $listener)
+ {
+ if (!isset($this->signals[$signal])) {
+ return;
+ }
+
+ $index = \array_search($listener, $this->signals[$signal], true);
+ unset($this->signals[$signal][$index]);
+
+ if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) {
+ unset($this->signals[$signal]);
+ }
+ }
+
+ public function call($signal)
+ {
+ if (!isset($this->signals[$signal])) {
+ return;
+ }
+
+ foreach ($this->signals[$signal] as $listener) {
+ \call_user_func($listener, $signal);
+ }
+ }
+
+ public function count($signal)
+ {
+ if (!isset($this->signals[$signal])) {
+ return 0;
+ }
+
+ return \count($this->signals[$signal]);
+ }
+
+ public function isEmpty()
+ {
+ return !$this->signals;
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/StreamSelectLoop.php b/assets/php/vendor/react/event-loop/src/StreamSelectLoop.php
new file mode 100644
index 0000000..e82e9e4
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/StreamSelectLoop.php
@@ -0,0 +1,275 @@
+futureTickQueue = new FutureTickQueue();
+ $this->timers = new Timers();
+ $this->pcntl = extension_loaded('pcntl');
+ $this->signals = new SignalsHandler();
+ }
+
+ public function addReadStream($stream, $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readStreams[$key])) {
+ $this->readStreams[$key] = $stream;
+ $this->readListeners[$key] = $listener;
+ }
+ }
+
+ public function addWriteStream($stream, $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeStreams[$key])) {
+ $this->writeStreams[$key] = $stream;
+ $this->writeListeners[$key] = $listener;
+ }
+ }
+
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->readStreams[$key],
+ $this->readListeners[$key]
+ );
+ }
+
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->writeStreams[$key],
+ $this->writeListeners[$key]
+ );
+ }
+
+ public function addTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, false);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ public function addPeriodicTimer($interval, $callback)
+ {
+ $timer = new Timer($interval, $callback, true);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ public function cancelTimer(TimerInterface $timer)
+ {
+ $this->timers->cancel($timer);
+ }
+
+ public function futureTick($listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ public function addSignal($signal, $listener)
+ {
+ if ($this->pcntl === false) {
+ throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"');
+ }
+
+ $first = $this->signals->count($signal) === 0;
+ $this->signals->add($signal, $listener);
+
+ if ($first) {
+ \pcntl_signal($signal, array($this->signals, 'call'));
+ }
+ }
+
+ public function removeSignal($signal, $listener)
+ {
+ if (!$this->signals->count($signal)) {
+ return;
+ }
+
+ $this->signals->remove($signal, $listener);
+
+ if ($this->signals->count($signal) === 0) {
+ \pcntl_signal($signal, SIG_DFL);
+ }
+ }
+
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->futureTickQueue->tick();
+
+ $this->timers->tick();
+
+ // Future-tick queue has pending callbacks ...
+ if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+ $timeout = 0;
+
+ // There is a pending timer, only block until it is due ...
+ } elseif ($scheduledAt = $this->timers->getFirst()) {
+ $timeout = $scheduledAt - $this->timers->getTime();
+ if ($timeout < 0) {
+ $timeout = 0;
+ } else {
+ // Convert float seconds to int microseconds.
+ // Ensure we do not exceed maximum integer size, which may
+ // cause the loop to tick once every ~35min on 32bit systems.
+ $timeout *= self::MICROSECONDS_PER_SECOND;
+ $timeout = $timeout > PHP_INT_MAX ? PHP_INT_MAX : (int)$timeout;
+ }
+
+ // The only possible event is stream or signal activity, so wait forever ...
+ } elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) {
+ $timeout = null;
+
+ // There's nothing left to do ...
+ } else {
+ break;
+ }
+
+ $this->waitForStreamActivity($timeout);
+ }
+ }
+
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Wait/check for stream activity, or until the next timer is due.
+ *
+ * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
+ */
+ private function waitForStreamActivity($timeout)
+ {
+ $read = $this->readStreams;
+ $write = $this->writeStreams;
+
+ $available = $this->streamSelect($read, $write, $timeout);
+ if ($this->pcntl) {
+ \pcntl_signal_dispatch();
+ }
+ if (false === $available) {
+ // if a system call has been interrupted,
+ // we cannot rely on it's outcome
+ return;
+ }
+
+ foreach ($read as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream);
+ }
+ }
+
+ foreach ($write as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream);
+ }
+ }
+ }
+
+ /**
+ * Emulate a stream_select() implementation that does not break when passed
+ * empty stream arrays.
+ *
+ * @param array &$read An array of read streams to select upon.
+ * @param array &$write An array of write streams to select upon.
+ * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
+ *
+ * @return integer|false The total number of streams that are ready for read/write.
+ * Can return false if stream_select() is interrupted by a signal.
+ */
+ private function streamSelect(array &$read, array &$write, $timeout)
+ {
+ if ($read || $write) {
+ $except = null;
+
+ // suppress warnings that occur, when stream_select is interrupted by a signal
+ return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
+ }
+
+ $timeout && usleep($timeout);
+
+ return 0;
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/Tick/FutureTickQueue.php b/assets/php/vendor/react/event-loop/src/Tick/FutureTickQueue.php
new file mode 100644
index 0000000..c79afc5
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/Tick/FutureTickQueue.php
@@ -0,0 +1,60 @@
+queue = new SplQueue();
+ }
+
+ /**
+ * Add a callback to be invoked on a future tick of the event loop.
+ *
+ * Callbacks are guaranteed to be executed in the order they are enqueued.
+ *
+ * @param callable $listener The callback to invoke.
+ */
+ public function add($listener)
+ {
+ $this->queue->enqueue($listener);
+ }
+
+ /**
+ * Flush the callback queue.
+ */
+ public function tick()
+ {
+ // Only invoke as many callbacks as were on the queue when tick() was called.
+ $count = $this->queue->count();
+
+ while ($count--) {
+ call_user_func(
+ $this->queue->dequeue()
+ );
+ }
+ }
+
+ /**
+ * Check if the next tick queue is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->queue->isEmpty();
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/Timer/Timer.php b/assets/php/vendor/react/event-loop/src/Timer/Timer.php
new file mode 100644
index 0000000..da3602a
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/Timer/Timer.php
@@ -0,0 +1,55 @@
+interval = (float) $interval;
+ $this->callback = $callback;
+ $this->periodic = (bool) $periodic;
+ }
+
+ public function getInterval()
+ {
+ return $this->interval;
+ }
+
+ public function getCallback()
+ {
+ return $this->callback;
+ }
+
+ public function isPeriodic()
+ {
+ return $this->periodic;
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/Timer/Timers.php b/assets/php/vendor/react/event-loop/src/Timer/Timers.php
new file mode 100644
index 0000000..17bbdac
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/Timer/Timers.php
@@ -0,0 +1,109 @@
+timers = new SplObjectStorage();
+ $this->scheduler = new SplPriorityQueue();
+ }
+
+ public function updateTime()
+ {
+ return $this->time = microtime(true);
+ }
+
+ public function getTime()
+ {
+ return $this->time ?: $this->updateTime();
+ }
+
+ public function add(TimerInterface $timer)
+ {
+ $interval = $timer->getInterval();
+ $scheduledAt = $interval + microtime(true);
+
+ $this->timers->attach($timer, $scheduledAt);
+ $this->scheduler->insert($timer, -$scheduledAt);
+ }
+
+ public function contains(TimerInterface $timer)
+ {
+ return $this->timers->contains($timer);
+ }
+
+ public function cancel(TimerInterface $timer)
+ {
+ $this->timers->detach($timer);
+ }
+
+ public function getFirst()
+ {
+ while ($this->scheduler->count()) {
+ $timer = $this->scheduler->top();
+
+ if ($this->timers->contains($timer)) {
+ return $this->timers[$timer];
+ }
+
+ $this->scheduler->extract();
+ }
+
+ return null;
+ }
+
+ public function isEmpty()
+ {
+ return count($this->timers) === 0;
+ }
+
+ public function tick()
+ {
+ $time = $this->updateTime();
+ $timers = $this->timers;
+ $scheduler = $this->scheduler;
+
+ while (!$scheduler->isEmpty()) {
+ $timer = $scheduler->top();
+
+ if (!isset($timers[$timer])) {
+ $scheduler->extract();
+ $timers->detach($timer);
+
+ continue;
+ }
+
+ if ($timers[$timer] >= $time) {
+ break;
+ }
+
+ $scheduler->extract();
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($timer->isPeriodic() && isset($timers[$timer])) {
+ $timers[$timer] = $scheduledAt = $timer->getInterval() + $time;
+ $scheduler->insert($timer, -$scheduledAt);
+ } else {
+ $timers->detach($timer);
+ }
+ }
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/src/TimerInterface.php b/assets/php/vendor/react/event-loop/src/TimerInterface.php
new file mode 100644
index 0000000..cdcf773
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/src/TimerInterface.php
@@ -0,0 +1,27 @@
+tickTimeout = 0.02;
+ $this->loop = $this->createLoop();
+ }
+
+ abstract public function createLoop();
+
+ public function createSocketPair()
+ {
+ $domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX;
+ $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ foreach ($sockets as $socket) {
+ if (function_exists('stream_set_read_buffer')) {
+ stream_set_read_buffer($socket, 0);
+ }
+ }
+
+ return $sockets;
+ }
+
+ public function testAddReadStream()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+
+ fwrite($output, "foo\n");
+ $this->tickLoop($this->loop);
+
+ fwrite($output, "bar\n");
+ $this->tickLoop($this->loop);
+ }
+
+ public function testAddReadStreamIgnoresSecondCallable()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+
+ fwrite($output, "foo\n");
+ $this->tickLoop($this->loop);
+
+ fwrite($output, "bar\n");
+ $this->tickLoop($this->loop);
+ }
+
+ public function testAddReadStreamReceivesDataFromStreamReference()
+ {
+ $this->received = '';
+ $this->subAddReadStreamReceivesDataFromStreamReference();
+ $this->assertEquals('', $this->received);
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ $this->assertEquals('[hello]X', $this->received);
+ }
+
+ /**
+ * Helper for above test. This happens in another helper method to verify
+ * the loop keeps track of assigned stream resources (refcount).
+ */
+ private function subAddReadStreamReceivesDataFromStreamReference()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ fwrite($input, 'hello');
+ fclose($input);
+
+ $loop = $this->loop;
+ $received =& $this->received;
+ $loop->addReadStream($output, function ($output) use ($loop, &$received) {
+ $chunk = fread($output, 1024);
+ if ($chunk === '') {
+ $received .= 'X';
+ $loop->removeReadStream($output);
+ fclose($output);
+ } else {
+ $received .= '[' . $chunk . ']';
+ }
+ });
+ }
+
+ public function testAddWriteStream()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
+ $this->tickLoop($this->loop);
+ $this->tickLoop($this->loop);
+ }
+
+ public function testAddWriteStreamIgnoresSecondCallable()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->tickLoop($this->loop);
+ $this->tickLoop($this->loop);
+ }
+
+ public function testRemoveReadStreamInstantly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "bar\n");
+ $this->tickLoop($this->loop);
+ }
+
+ public function testRemoveReadStreamAfterReading()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+
+ fwrite($output, "foo\n");
+ $this->tickLoop($this->loop);
+
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "bar\n");
+ $this->tickLoop($this->loop);
+ }
+
+ public function testRemoveWriteStreamInstantly()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->removeWriteStream($input);
+ $this->tickLoop($this->loop);
+ }
+
+ public function testRemoveWriteStreamAfterWriting()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableOnce());
+ $this->tickLoop($this->loop);
+
+ $this->loop->removeWriteStream($input);
+ $this->tickLoop($this->loop);
+ }
+
+ public function testRemoveStreamForReadOnly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->addWriteStream($output, $this->expectCallableOnce());
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "foo\n");
+ $this->tickLoop($this->loop);
+ }
+
+ public function testRemoveStreamForWriteOnly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ fwrite($output, "foo\n");
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+ $this->loop->addWriteStream($output, $this->expectCallableNever());
+ $this->loop->removeWriteStream($output);
+
+ $this->tickLoop($this->loop);
+ }
+
+ public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesEndsLoop()
+ {
+ list($stream, $other) = $this->createSocketPair();
+ stream_set_blocking($stream, false);
+ stream_set_blocking($other, false);
+
+ // dummy writable handler
+ $this->loop->addWriteStream($stream, function () { });
+
+ // remove stream when the stream is readable (closes)
+ $loop = $this->loop;
+ $loop->addReadStream($stream, function ($stream) use ($loop) {
+ $loop->removeReadStream($stream);
+ $loop->removeWriteStream($stream);
+ fclose($stream);
+ });
+
+ // close other side
+ fclose($other);
+
+ $this->assertRunFasterThan($this->tickTimeout);
+ }
+
+ public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesOnEndOfFileEndsLoop()
+ {
+ list($stream, $other) = $this->createSocketPair();
+ stream_set_blocking($stream, false);
+ stream_set_blocking($other, false);
+
+ // dummy writable handler
+ $this->loop->addWriteStream($stream, function () { });
+
+ // remove stream when the stream is readable (closes)
+ $loop = $this->loop;
+ $loop->addReadStream($stream, function ($stream) use ($loop) {
+ $data = fread($stream, 1024);
+ if ($data !== '') {
+ return;
+ }
+
+ $loop->removeReadStream($stream);
+ $loop->removeWriteStream($stream);
+ fclose($stream);
+ });
+
+ // send data and close stream
+ fwrite($other, str_repeat('.', static::PHP_DEFAULT_CHUNK_SIZE));
+ $this->loop->addTimer(0.01, function () use ($other) {
+ fclose($other);
+ });
+
+ $this->assertRunFasterThan(0.1);
+ }
+
+ public function testRemoveReadAndWriteStreamFromLoopWithClosingResourceEndsLoop()
+ {
+ // get only one part of the pair to ensure the other side will close immediately
+ list($stream) = $this->createSocketPair();
+ stream_set_blocking($stream, false);
+
+ // dummy writable handler
+ $this->loop->addWriteStream($stream, function () { });
+
+ // remove stream when the stream is readable (closes)
+ $loop = $this->loop;
+ $loop->addReadStream($stream, function ($stream) use ($loop) {
+ $loop->removeReadStream($stream);
+ $loop->removeWriteStream($stream);
+ fclose($stream);
+ });
+
+ $this->assertRunFasterThan($this->tickTimeout);
+ }
+
+ public function testRemoveInvalid()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ // remove a valid stream from the event loop that was never added in the first place
+ $this->loop->removeReadStream($stream);
+ $this->loop->removeWriteStream($stream);
+
+ $this->assertTrue(true);
+ }
+
+ /** @test */
+ public function emptyRunShouldSimplyReturn()
+ {
+ $this->assertRunFasterThan($this->tickTimeout);
+ }
+
+ /** @test */
+ public function runShouldReturnWhenNoMoreFds()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addReadStream($input, function ($stream) use ($loop) {
+ $loop->removeReadStream($stream);
+ });
+
+ fwrite($output, "foo\n");
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ /** @test */
+ public function stopShouldStopRunningLoop()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addReadStream($input, function ($stream) use ($loop) {
+ $loop->stop();
+ });
+
+ fwrite($output, "foo\n");
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ public function testStopShouldPreventRunFromBlocking()
+ {
+ $that = $this;
+ $this->loop->addTimer(
+ 1,
+ function () use ($that) {
+ $that->fail('Timer was executed.');
+ }
+ );
+
+ $loop = $this->loop;
+ $this->loop->futureTick(
+ function () use ($loop) {
+ $loop->stop();
+ }
+ );
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ public function testIgnoreRemovedCallback()
+ {
+ // two independent streams, both should be readable right away
+ list ($input1, $output1) = $this->createSocketPair();
+ list ($input2, $output2) = $this->createSocketPair();
+
+ $called = false;
+
+ $loop = $this->loop;
+ $loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) {
+ // stream1 is readable, remove stream2 as well => this will invalidate its callback
+ $loop->removeReadStream($stream);
+ $loop->removeReadStream($input2);
+
+ $called = true;
+ });
+
+ // this callback would have to be called as well, but the first stream already removed us
+ $that = $this;
+ $loop->addReadStream($input2, function () use (& $called, $that) {
+ if ($called) {
+ $that->fail('Callback 2 must not be called after callback 1 was called');
+ }
+ });
+
+ fwrite($output1, "foo\n");
+ fwrite($output2, "foo\n");
+
+ $loop->run();
+
+ $this->assertTrue($called);
+ }
+
+ public function testFutureTickEventGeneratedByFutureTick()
+ {
+ $loop = $this->loop;
+ $this->loop->futureTick(
+ function () use ($loop) {
+ $loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTick()
+ {
+ $called = false;
+
+ $callback = function () use (&$called) {
+ $called = true;
+ };
+
+ $this->loop->futureTick($callback);
+
+ $this->assertFalse($called);
+
+ $this->tickLoop($this->loop);
+
+ $this->assertTrue($called);
+ }
+
+ public function testFutureTickFiresBeforeIO()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->tickLoop($this->loop);
+ }
+
+ public function testRecursiveFutureTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream, $loop) {
+ echo 'stream' . PHP_EOL;
+ $loop->removeWriteStream($stream);
+ }
+ );
+
+ $this->loop->futureTick(
+ function () use ($loop) {
+ echo 'future-tick-1' . PHP_EOL;
+ $loop->futureTick(
+ function () {
+ echo 'future-tick-2' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testRunWaitsForFutureTickEvents()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream, $loop) {
+ $loop->removeWriteStream($stream);
+ $loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTickEventGeneratedByTimer()
+ {
+ $loop = $this->loop;
+ $this->loop->addTimer(
+ 0.001,
+ function () use ($loop) {
+ $loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testRemoveSignalNotRegisteredIsNoOp()
+ {
+ $this->loop->removeSignal(SIGINT, function () { });
+ $this->assertTrue(true);
+ }
+
+ public function testSignal()
+ {
+ if (!function_exists('posix_kill') || !function_exists('posix_getpid')) {
+ $this->markTestSkipped('Signal test skipped because functions "posix_kill" and "posix_getpid" are missing.');
+ }
+
+ $called = false;
+ $calledShouldNot = true;
+
+ $timer = $this->loop->addPeriodicTimer(1, function () {});
+
+ $this->loop->addSignal(SIGUSR2, $func2 = function () use (&$calledShouldNot) {
+ $calledShouldNot = false;
+ });
+
+ $loop = $this->loop;
+ $this->loop->addSignal(SIGUSR1, $func1 = function () use (&$func1, &$func2, &$called, $timer, $loop) {
+ $called = true;
+ $loop->removeSignal(SIGUSR1, $func1);
+ $loop->removeSignal(SIGUSR2, $func2);
+ $loop->cancelTimer($timer);
+ });
+
+ $this->loop->futureTick(function () {
+ posix_kill(posix_getpid(), SIGUSR1);
+ });
+
+ $this->loop->run();
+
+ $this->assertTrue($called);
+ $this->assertTrue($calledShouldNot);
+ }
+
+ public function testSignalMultipleUsagesForTheSameListener()
+ {
+ $funcCallCount = 0;
+ $func = function () use (&$funcCallCount) {
+ $funcCallCount++;
+ };
+ $this->loop->addTimer(1, function () {});
+
+ $this->loop->addSignal(SIGUSR1, $func);
+ $this->loop->addSignal(SIGUSR1, $func);
+
+ $this->loop->addTimer(0.4, function () {
+ posix_kill(posix_getpid(), SIGUSR1);
+ });
+ $loop = $this->loop;
+ $this->loop->addTimer(0.9, function () use (&$func, $loop) {
+ $loop->removeSignal(SIGUSR1, $func);
+ });
+
+ $this->loop->run();
+
+ $this->assertSame(1, $funcCallCount);
+ }
+
+ public function testSignalsKeepTheLoopRunning()
+ {
+ $loop = $this->loop;
+ $function = function () {};
+ $this->loop->addSignal(SIGUSR1, $function);
+ $this->loop->addTimer(1.5, function () use ($function, $loop) {
+ $loop->removeSignal(SIGUSR1, $function);
+ $loop->stop();
+ });
+
+ $this->assertRunSlowerThan(1.5);
+ }
+
+ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop()
+ {
+ $loop = $this->loop;
+ $function = function () {};
+ $this->loop->addSignal(SIGUSR1, $function);
+ $this->loop->addTimer(1.5, function () use ($function, $loop) {
+ $loop->removeSignal(SIGUSR1, $function);
+ });
+
+ $this->assertRunFasterThan(1.6);
+ }
+
+ public function testTimerIntervalCanBeFarInFuture()
+ {
+ // get only one part of the pair to ensure the other side will close immediately
+ list($stream) = $this->createSocketPair();
+
+ // start a timer very far in the future
+ $timer = $this->loop->addTimer(PHP_INT_MAX, function () { });
+
+ // remove stream and timer when the stream is readable (closes)
+ $loop = $this->loop;
+ $this->loop->addReadStream($stream, function ($stream) use ($timer, $loop) {
+ $loop->removeReadStream($stream);
+ $loop->cancelTimer($timer);
+ });
+
+ $this->assertRunFasterThan($this->tickTimeout);
+ }
+
+ private function assertRunSlowerThan($minInterval)
+ {
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertLessThan($interval, $minInterval);
+ }
+
+ private function assertRunFasterThan($maxInterval)
+ {
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertLessThan($maxInterval, $interval);
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/CallableStub.php b/assets/php/vendor/react/event-loop/tests/CallableStub.php
new file mode 100644
index 0000000..913d403
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/CallableStub.php
@@ -0,0 +1,10 @@
+markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.');
+ }
+
+ return new ExtEvLoop();
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/ExtEventLoopTest.php b/assets/php/vendor/react/event-loop/tests/ExtEventLoopTest.php
new file mode 100644
index 0000000..2f88d18
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/ExtEventLoopTest.php
@@ -0,0 +1,84 @@
+markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
+ }
+
+ if (!extension_loaded('event')) {
+ $this->markTestSkipped('ext-event tests skipped because ext-event is not installed.');
+ }
+
+ return new ExtEventLoop();
+ }
+
+ public function createStream()
+ {
+ // Use a FIFO on linux to get around lack of support for disk-based file
+ // descriptors when using the EPOLL back-end.
+ if ('Linux' === PHP_OS) {
+ $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
+
+ unlink($this->fifoPath);
+
+ posix_mkfifo($this->fifoPath, 0600);
+
+ $stream = fopen($this->fifoPath, 'r+');
+
+ // ext-event (as of 1.8.1) does not yet support in-memory temporary
+ // streams. Setting maxmemory:0 and performing a write forces PHP to
+ // back this temporary stream with a real file.
+ //
+ // This problem is mentioned at https://bugs.php.net/bug.php?id=64652&edit=3
+ // but remains unresolved (despite that issue being closed).
+ } else {
+ $stream = fopen('php://temp/maxmemory:0', 'r+');
+
+ fwrite($stream, 'x');
+ ftruncate($stream, 0);
+ }
+
+ return $stream;
+ }
+
+ public function writeToStream($stream, $content)
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::writeToStream($stream, $content);
+ }
+
+ fwrite($stream, $content);
+ }
+
+ /**
+ * @group epoll-readable-error
+ */
+ public function testCanUseReadableStreamWithFeatureFds()
+ {
+ if (PHP_VERSION_ID > 70000) {
+ $this->markTestSkipped('Memory stream not supported');
+ }
+
+ $this->loop = $this->createLoop(true);
+
+ $input = fopen('php://temp/maxmemory:0', 'r+');
+
+ fwrite($input, 'x');
+ ftruncate($input, 0);
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+
+ fwrite($input, "foo\n");
+ $this->tickLoop($this->loop);
+
+ fwrite($input, "bar\n");
+ $this->tickLoop($this->loop);
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/ExtLibevLoopTest.php b/assets/php/vendor/react/event-loop/tests/ExtLibevLoopTest.php
new file mode 100644
index 0000000..19a5e87
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/ExtLibevLoopTest.php
@@ -0,0 +1,22 @@
+markTestSkipped('libev tests skipped because ext-libev is not installed.');
+ }
+
+ return new ExtLibevLoop();
+ }
+
+ public function testLibEvConstructor()
+ {
+ $loop = new ExtLibevLoop();
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/ExtLibeventLoopTest.php b/assets/php/vendor/react/event-loop/tests/ExtLibeventLoopTest.php
new file mode 100644
index 0000000..8497065
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/ExtLibeventLoopTest.php
@@ -0,0 +1,58 @@
+markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
+ }
+
+ if (!function_exists('event_base_new')) {
+ $this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
+ }
+
+ return new ExtLibeventLoop();
+ }
+
+ public function tearDown()
+ {
+ if (file_exists($this->fifoPath)) {
+ unlink($this->fifoPath);
+ }
+ }
+
+ public function createStream()
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::createStream();
+ }
+
+ $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
+
+ unlink($this->fifoPath);
+
+ // Use a FIFO on linux to get around lack of support for disk-based file
+ // descriptors when using the EPOLL back-end.
+ posix_mkfifo($this->fifoPath, 0600);
+
+ $stream = fopen($this->fifoPath, 'r+');
+
+ return $stream;
+ }
+
+ public function writeToStream($stream, $content)
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::writeToStream($stream, $content);
+ }
+
+ fwrite($stream, $content);
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/SignalsHandlerTest.php b/assets/php/vendor/react/event-loop/tests/SignalsHandlerTest.php
new file mode 100644
index 0000000..f8b7df3
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/SignalsHandlerTest.php
@@ -0,0 +1,55 @@
+assertSame(0, $callCount);
+
+ $signals->add(SIGUSR1, $func);
+ $this->assertSame(0, $callCount);
+
+ $signals->add(SIGUSR1, $func);
+ $this->assertSame(0, $callCount);
+
+ $signals->add(SIGUSR1, $func);
+ $this->assertSame(0, $callCount);
+
+ $signals->call(SIGUSR1);
+ $this->assertSame(1, $callCount);
+
+ $signals->add(SIGUSR2, $func);
+ $this->assertSame(1, $callCount);
+
+ $signals->add(SIGUSR2, $func);
+ $this->assertSame(1, $callCount);
+
+ $signals->call(SIGUSR2);
+ $this->assertSame(2, $callCount);
+
+ $signals->remove(SIGUSR2, $func);
+ $this->assertSame(2, $callCount);
+
+ $signals->remove(SIGUSR2, $func);
+ $this->assertSame(2, $callCount);
+
+ $signals->call(SIGUSR2);
+ $this->assertSame(2, $callCount);
+
+ $signals->remove(SIGUSR1, $func);
+ $this->assertSame(2, $callCount);
+
+ $signals->call(SIGUSR1);
+ $this->assertSame(2, $callCount);
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/StreamSelectLoopTest.php b/assets/php/vendor/react/event-loop/tests/StreamSelectLoopTest.php
new file mode 100644
index 0000000..bd19e1c
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/StreamSelectLoopTest.php
@@ -0,0 +1,148 @@
+getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
+ $this->resetSignalHandlers();
+ }
+ }
+
+ public function createLoop()
+ {
+ return new StreamSelectLoop();
+ }
+
+ public function testStreamSelectTimeoutEmulation()
+ {
+ $this->loop->addTimer(
+ 0.05,
+ $this->expectCallableOnce()
+ );
+
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertGreaterThan(0.04, $interval);
+ }
+
+ public function signalProvider()
+ {
+ return array(
+ array('SIGUSR1'),
+ array('SIGHUP'),
+ array('SIGTERM'),
+ );
+ }
+
+ /**
+ * Test signal interrupt when no stream is attached to the loop
+ * @dataProvider signalProvider
+ */
+ public function testSignalInterruptNoStream($signal)
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('"pcntl" extension is required to run this test.');
+ }
+
+ // dispatch signal handler every 10ms for 0.1s
+ $check = $this->loop->addPeriodicTimer(0.01, function() {
+ pcntl_signal_dispatch();
+ });
+ $loop = $this->loop;
+ $loop->addTimer(0.1, function () use ($check, $loop) {
+ $loop->cancelTimer($check);
+ });
+
+ $handled = false;
+ $this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) {
+ $handled = true;
+ }));
+
+ // spawn external process to send signal to current process id
+ $this->forkSendSignal($signal);
+
+ $this->loop->run();
+ $this->assertTrue($handled);
+ }
+
+ /**
+ * Test signal interrupt when a stream is attached to the loop
+ * @dataProvider signalProvider
+ */
+ public function testSignalInterruptWithStream($signal)
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('"pcntl" extension is required to run this test.');
+ }
+
+ // dispatch signal handler every 10ms
+ $this->loop->addPeriodicTimer(0.01, function() {
+ pcntl_signal_dispatch();
+ });
+
+ // add stream to the loop
+ $loop = $this->loop;
+ list($writeStream, $readStream) = $this->createSocketPair();
+ $loop->addReadStream($readStream, function ($stream) use ($loop) {
+ /** @var $loop LoopInterface */
+ $read = fgets($stream);
+ if ($read === "end loop\n") {
+ $loop->stop();
+ }
+ });
+ $this->loop->addTimer(0.1, function() use ($writeStream) {
+ fwrite($writeStream, "end loop\n");
+ });
+
+ $handled = false;
+ $this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) {
+ $handled = true;
+ }));
+
+ // spawn external process to send signal to current process id
+ $this->forkSendSignal($signal);
+
+ $this->loop->run();
+
+ $this->assertTrue($handled);
+ }
+
+ /**
+ * reset all signal handlers to default
+ */
+ protected function resetSignalHandlers()
+ {
+ foreach($this->signalProvider() as $signal) {
+ pcntl_signal(constant($signal[0]), SIG_DFL);
+ }
+ }
+
+ /**
+ * fork child process to send signal to current process id
+ */
+ protected function forkSendSignal($signal)
+ {
+ $currentPid = posix_getpid();
+ $childPid = pcntl_fork();
+ if ($childPid == -1) {
+ $this->fail("Failed to fork child process!");
+ } else if ($childPid === 0) {
+ // this is executed in the child process
+ usleep(20000);
+ posix_kill($currentPid, constant($signal));
+ die();
+ }
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/TestCase.php b/assets/php/vendor/react/event-loop/tests/TestCase.php
new file mode 100644
index 0000000..dbdd54c
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/TestCase.php
@@ -0,0 +1,53 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\EventLoop\CallableStub')->getMock();
+ }
+
+ protected function tickLoop(LoopInterface $loop)
+ {
+ $loop->futureTick(function () use ($loop) {
+ $loop->stop();
+ });
+
+ $loop->run();
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php
new file mode 100644
index 0000000..294e683
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php
@@ -0,0 +1,122 @@
+createLoop();
+
+ $timer = $loop->addTimer(0.001, $this->expectCallableNever());
+
+ $this->assertInstanceOf('React\EventLoop\TimerInterface', $timer);
+ $this->assertFalse($timer->isPeriodic());
+ }
+
+ public function testAddTimerWillBeInvokedOnceAndBlocksLoopWhenRunning()
+ {
+ $loop = $this->createLoop();
+
+ $loop->addTimer(0.001, $this->expectCallableOnce());
+
+ $start = microtime(true);
+ $loop->run();
+ $end = microtime(true);
+
+ // make no strict assumptions about actual time interval.
+ // must be at least 0.001s (1ms) and should not take longer than 0.1s
+ $this->assertGreaterThanOrEqual(0.001, $end - $start);
+ $this->assertLessThan(0.1, $end - $start);
+ }
+
+ public function testAddPeriodicTimerReturnsPeriodicTimerInstance()
+ {
+ $loop = $this->createLoop();
+
+ $periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableNever());
+
+ $this->assertInstanceOf('React\EventLoop\TimerInterface', $periodic);
+ $this->assertTrue($periodic->isPeriodic());
+ }
+
+ public function testAddPeriodicTimerWillBeInvokedUntilItIsCancelled()
+ {
+ $loop = $this->createLoop();
+
+ $periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableExactly(3));
+
+ // make no strict assumptions about actual time interval.
+ // leave some room to ensure this ticks exactly 3 times.
+ $loop->addTimer(0.399, function () use ($loop, $periodic) {
+ $loop->cancelTimer($periodic);
+ });
+
+ $loop->run();
+ }
+
+ public function testAddPeriodicTimerWillBeInvokedWithMaximumAccuracyUntilItIsCancelled()
+ {
+ $loop = $this->createLoop();
+
+ $i = 0;
+ $periodic = $loop->addPeriodicTimer(0.001, function () use (&$i) {
+ ++$i;
+ });
+
+ $loop->addTimer(0.02, function () use ($loop, $periodic) {
+ $loop->cancelTimer($periodic);
+ });
+
+ $loop->run();
+
+ // make no strict assumptions about number of invocations.
+ // we know it must be no more than 20 times and should at least be
+ // invoked twice for really slow loops
+ $this->assertLessThanOrEqual(20, $i);
+ $this->assertGreaterThan(2, $i);
+ }
+
+ public function testAddPeriodicTimerCancelsItself()
+ {
+ $loop = $this->createLoop();
+
+ $i = 0;
+ $loop->addPeriodicTimer(0.001, function ($timer) use (&$i, $loop) {
+ $i++;
+
+ if ($i === 5) {
+ $loop->cancelTimer($timer);
+ }
+ });
+
+ $start = microtime(true);
+ $loop->run();
+ $end = microtime(true);
+
+ $this->assertEquals(5, $i);
+
+ // make no strict assumptions about time interval.
+ // 5 invocations must take at least 0.005s (5ms) and should not take
+ // longer than 0.1s for slower loops.
+ $this->assertGreaterThanOrEqual(0.005, $end - $start);
+ $this->assertLessThan(0.1, $end - $start);
+ }
+
+ public function testMinimumIntervalOneMicrosecond()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addTimer(0, function () {});
+
+ $this->assertEquals(0.000001, $timer->getInterval());
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/Timer/ExtEvTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/ExtEvTimerTest.php
new file mode 100644
index 0000000..bfa9186
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/Timer/ExtEvTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.');
+ }
+
+ return new ExtEvLoop();
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php
new file mode 100644
index 0000000..a7a6d00
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('ext-event tests skipped because ext-event is not installed.');
+ }
+
+ return new ExtEventLoop();
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/Timer/ExtLibevTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/ExtLibevTimerTest.php
new file mode 100644
index 0000000..65e82be
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/Timer/ExtLibevTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('libev tests skipped because ext-libev is not installed.');
+ }
+
+ return new ExtLibevLoop();
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/Timer/ExtLibeventTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/ExtLibeventTimerTest.php
new file mode 100644
index 0000000..9089b9a
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/Timer/ExtLibeventTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
+ }
+
+ return new ExtLibeventLoop();
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php b/assets/php/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php
new file mode 100644
index 0000000..cfe1d7d
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php
@@ -0,0 +1,13 @@
+tick();
+
+ // simulate a bunch of processing on stream events,
+ // part of which schedules a future timer...
+ sleep(1);
+ $timers->add(new Timer(0.5, function () {
+ $this->fail("Timer shouldn't be called");
+ }));
+
+ $timers->tick();
+
+ $this->assertTrue(true);
+ }
+}
diff --git a/assets/php/vendor/react/event-loop/tests/bootstrap.php b/assets/php/vendor/react/event-loop/tests/bootstrap.php
new file mode 100644
index 0000000..ea7dd4c
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/tests/bootstrap.php
@@ -0,0 +1,15 @@
+addPsr4('React\\Tests\\EventLoop\\', __DIR__);
+
+if (!defined('SIGUSR1')) {
+ define('SIGUSR1', 1);
+}
+
+if (!defined('SIGUSR2')) {
+ define('SIGUSR2', 2);
+}
diff --git a/assets/php/vendor/react/event-loop/travis-init.sh b/assets/php/vendor/react/event-loop/travis-init.sh
new file mode 100755
index 0000000..29ce884
--- /dev/null
+++ b/assets/php/vendor/react/event-loop/travis-init.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+set -e
+set -o pipefail
+
+if [[ "$TRAVIS_PHP_VERSION" != "hhvm" &&
+ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then
+
+ # install 'event' and 'ev' PHP extension
+ if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then
+ echo "yes" | pecl install event
+ echo "yes" | pecl install ev
+ fi
+
+ # install 'libevent' PHP extension (does not support php 7)
+ if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
+ "$TRAVIS_PHP_VERSION" != "7.1" &&
+ "$TRAVIS_PHP_VERSION" != "7.2" ]]; then
+ curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz
+ pushd libevent-0.1.0
+ phpize
+ ./configure
+ make
+ make install
+ popd
+ echo "extension=libevent.so" >> "$(php -r 'echo php_ini_loaded_file();')"
+ fi
+
+ # install 'libev' PHP extension (does not support php 7)
+ if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
+ "$TRAVIS_PHP_VERSION" != "7.1" &&
+ "$TRAVIS_PHP_VERSION" != "7.2" ]]; then
+ git clone --recursive https://github.com/m4rw3r/php-libev
+ pushd php-libev
+ phpize
+ ./configure --with-libev
+ make
+ make install
+ popd
+ echo "extension=libev.so" >> "$(php -r 'echo php_ini_loaded_file();')"
+ fi
+
+fi
diff --git a/assets/php/vendor/react/promise-timer/.gitignore b/assets/php/vendor/react/promise-timer/.gitignore
new file mode 100644
index 0000000..de4a392
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/.gitignore
@@ -0,0 +1,2 @@
+/vendor
+/composer.lock
diff --git a/assets/php/vendor/react/promise-timer/.travis.yml b/assets/php/vendor/react/promise-timer/.travis.yml
new file mode 100644
index 0000000..a71864a
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/.travis.yml
@@ -0,0 +1,26 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/assets/php/vendor/react/promise-timer/CHANGELOG.md b/assets/php/vendor/react/promise-timer/CHANGELOG.md
new file mode 100644
index 0000000..0a21244
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/CHANGELOG.md
@@ -0,0 +1,40 @@
+# Changelog
+
+## 1.2.1 (2017-12-22)
+
+* README improvements
+ (#28 by @jsor)
+
+* Improve test suite by adding forward compatiblity with PHPUnit 6 and
+ fix test suite forward compatibility with upcoming EventLoop releases
+ (#30 and #31 by @clue)
+
+## 1.2.0 (2017-08-08)
+
+* Feature: Only start timers if input Promise is still pending and
+ return a settled output promise if the input is already settled.
+ (#25 by @clue)
+
+* Feature: Cap minimum timer interval at 1µs across all versions
+ (#23 by @clue)
+
+* Feature: Forward compatibility with EventLoop v1.0 and v0.5
+ (#27 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev and
+ lock Travis distro so new defaults will not break the build
+ (#24 and #26 by @clue)
+
+## 1.1.1 (2016-12-27)
+
+* Improve test suite to use PSR-4 autoloader and proper namespaces.
+ (#21 by @clue)
+
+## 1.1.0 (2016-02-29)
+
+* Feature: Support promise cancellation for all timer primitives
+ (#18 by @clue)
+
+## 1.0.0 (2015-09-29)
+
+* First tagged release
diff --git a/assets/php/vendor/react/promise-timer/LICENSE b/assets/php/vendor/react/promise-timer/LICENSE
new file mode 100644
index 0000000..dc09d1e
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Christian Lück
+
+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.
diff --git a/assets/php/vendor/react/promise-timer/README.md b/assets/php/vendor/react/promise-timer/README.md
new file mode 100644
index 0000000..2ea94fa
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/README.md
@@ -0,0 +1,372 @@
+# PromiseTimer
+
+[](https://travis-ci.org/reactphp/promise-timer)
+
+A trivial implementation of timeouts for `Promise`s, built on top of [ReactPHP](https://reactphp.org/).
+
+**Table of contents**
+
+* [Usage](#usage)
+ * [timeout()](#timeout)
+ * [Timeout cancellation](#timeout-cancellation)
+ * [Cancellation handler](#cancellation-handler)
+ * [Input cancellation](#input-cancellation)
+ * [Output cancellation](#output-cancellation)
+ * [Collections](#collections)
+ * [resolve()](#resolve)
+ * [Resolve cancellation](#resolve-cancellation)
+ * [reject()](#reject)
+ * [Reject cancellation](#reject-cancellation)
+ * [TimeoutException](#timeoutexception)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Usage
+
+This lightweight library consists only of a few simple functions.
+All functions reside under the `React\Promise\Timer` namespace.
+
+The below examples assume you use an import statement similar to this:
+
+```php
+use React\Promise\Timer;
+
+Timer\timeout(…);
+```
+
+Alternatively, you can also refer to them with their fully-qualified name:
+
+```php
+\React\Promise\Timer\timeout(…);
+```
+
+### timeout()
+
+The `timeout(PromiseInterface $promise, $time, LoopInterface $loop)` function
+can be used to *cancel* operations that take *too long*.
+You need to pass in an input `$promise` that represents a pending operation and timeout parameters.
+It returns a new `Promise` with the following resolution behavior:
+
+* If the input `$promise` resolves before `$time` seconds, resolve the resulting promise with its fulfillment value.
+* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value.
+* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
+
+Internally, the given `$time` value will be used to start a timer that will
+*cancel* the pending operation once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+If the input `$promise` is already settled, then the resulting promise will
+resolve or reject immediately without starting a timer at all.
+
+A common use case for handling only resolved values looks like this:
+
+```php
+$promise = accessSomeRemoteResource();
+Timer\timeout($promise, 10.0, $loop)->then(function ($value) {
+ // the operation finished within 10.0 seconds
+});
+```
+
+A more complete example could look like this:
+
+```php
+$promise = accessSomeRemoteResource();
+Timer\timeout($promise, 10.0, $loop)->then(
+ function ($value) {
+ // the operation finished within 10.0 seconds
+ },
+ function ($error) {
+ if ($error instanceof Timer\TimeoutException) {
+ // the operation has failed due to a timeout
+ } else {
+ // the input operation has failed due to some other error
+ }
+ }
+);
+```
+
+Or if you're using [react/promise v2.2.0](https://github.com/reactphp/promise) or up:
+
+```php
+Timer\timeout($promise, 10.0, $loop)
+ ->then(function ($value) {
+ // the operation finished within 10.0 seconds
+ })
+ ->otherwise(function (Timer\TimeoutException $error) {
+ // the operation has failed due to a timeout
+ })
+ ->otherwise(function ($error) {
+ // the input operation has failed due to some other error
+ })
+;
+```
+
+#### Timeout cancellation
+
+As discussed above, the [`timeout()`](#timeout) function will *cancel* the
+underlying operation if it takes *too long*.
+This means that you can be sure the resulting promise will then be rejected
+with a [`TimeoutException`](#timeoutexception).
+
+However, what happens to the underlying input `$promise` is a bit more tricky:
+Once the timer fires, we will try to call
+[`$promise->cancel()`](https://github.com/reactphp/promise#cancellablepromiseinterfacecancel)
+on the input `$promise` which in turn invokes its [cancellation handler](#cancellation-handler).
+
+This means that it's actually up the input `$promise` to handle
+[cancellation support](https://github.com/reactphp/promise#cancellablepromiseinterface).
+
+* A common use case involves cleaning up any resources like open network sockets or
+ file handles or terminating external processes or timers.
+
+* If the given input `$promise` does not support cancellation, then this is a NO-OP.
+ This means that while the resulting promise will still be rejected, the underlying
+ input `$promise` may still be pending and can hence continue consuming resources.
+
+See the following chapter for more details on the cancellation handler.
+
+#### Cancellation handler
+
+For example, an implementation for the above operation could look like this:
+
+```php
+function accessSomeRemoteResource()
+{
+ return new Promise(
+ function ($resolve, $reject) use (&$socket) {
+ // this will be called once the promise is created
+ // a common use case involves opening any resources and eventually resolving
+ $socket = createSocket();
+ $socket->on('data', function ($data) use ($resolve) {
+ $resolve($data);
+ });
+ },
+ function ($resolve, $reject) use (&$socket) {
+ // this will be called once calling `cancel()` on this promise
+ // a common use case involves cleaning any resources and then rejecting
+ $socket->close();
+ $reject(new \RuntimeException('Operation cancelled'));
+ }
+ );
+}
+```
+
+In this example, calling `$promise->cancel()` will invoke the registered cancellation
+handler which then closes the network socket and rejects the `Promise` instance.
+
+If no cancellation handler is passed to the `Promise` constructor, then invoking
+its `cancel()` method it is effectively a NO-OP.
+This means that it may still be pending and can hence continue consuming resources.
+
+For more details on the promise cancellation, please refer to the
+[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).
+
+#### Input cancellation
+
+Irrespective of the timout handling, you can also explicitly `cancel()` the
+input `$promise` at any time.
+This means that the `timeout()` handling does not affect cancellation of the
+input `$promise`, as demonstrated in the following example:
+
+```php
+$promise = accessSomeRemoteResource();
+$timeout = Timer\timeout($promise, 10.0, $loop);
+
+$promise->cancel();
+```
+
+The registered [cancellation handler](#cancellation-handler) is responsible for
+handling the `cancel()` call:
+
+* A described above, a common use involves resource cleanup and will then *reject*
+ the `Promise`.
+ If the input `$promise` is being rejected, then the timeout will be aborted
+ and the resulting promise will also be rejected.
+* If the input `$promise` is still pending, then the timout will continue
+ running until the timer expires.
+ The same happens if the input `$promise` does not register a
+ [cancellation handler](#cancellation-handler).
+
+#### Output cancellation
+
+Similarily, you can also explicitly `cancel()` the resulting promise like this:
+
+```php
+$promise = accessSomeRemoteResource();
+$timeout = Timer\timeout($promise, 10.0, $loop);
+
+$timeout->cancel();
+```
+
+Note how this looks very similar to the above [input cancellation](#input-cancellation)
+example. Accordingly, it also behaves very similar.
+
+Calling `cancel()` on the resulting promise will merely try
+to `cancel()` the input `$promise`.
+This means that we do not take over responsibility of the outcome and it's
+entirely up to the input `$promise` to handle cancellation support.
+
+The registered [cancellation handler](#cancellation-handler) is responsible for
+handling the `cancel()` call:
+
+* As described above, a common use involves resource cleanup and will then *reject*
+ the `Promise`.
+ If the input `$promise` is being rejected, then the timeout will be aborted
+ and the resulting promise will also be rejected.
+* If the input `$promise` is still pending, then the timout will continue
+ running until the timer expires.
+ The same happens if the input `$promise` does not register a
+ [cancellation handler](#cancellation-handler).
+
+To re-iterate, note that calling `cancel()` on the resulting promise will merely
+try to cancel the input `$promise` only.
+It is then up to the cancellation handler of the input promise to settle the promise.
+If the input promise is still pending when the timeout occurs, then the normal
+[timeout cancellation](#timeout-cancellation) handling will trigger, effectively rejecting
+the output promise with a [`TimeoutException`](#timeoutexception).
+
+This is done for consistency with the [timeout cancellation](#timeout-cancellation)
+handling and also because it is assumed this is often used like this:
+
+```php
+$timeout = Timer\timeout(accessSomeRemoteResource(), 10.0, $loop);
+
+$timeout->cancel();
+```
+
+As described above, this example works as expected and cleans up any resources
+allocated for the input `$promise`.
+
+Note that if the given input `$promise` does not support cancellation, then this
+is a NO-OP.
+This means that while the resulting promise will still be rejected after the
+timeout, the underlying input `$promise` may still be pending and can hence
+continue consuming resources.
+
+#### Collections
+
+If you want to wait for multiple promises to resolve, you can use the normal promise primitives like this:
+
+```php
+$promises = array(
+ accessSomeRemoteResource(),
+ accessSomeRemoteResource(),
+ accessSomeRemoteResource()
+);
+
+$promise = \React\Promise\all($promises);
+
+Timer\timeout($promise, 10, $loop)->then(function ($values) {
+ // *all* promises resolved
+});
+```
+
+The applies to all promise collection primitives alike, i.e. `all()`, `race()`, `any()`, `some()` etc.
+
+For more details on the promise primitives, please refer to the
+[Promise documentation](https://github.com/reactphp/promise#functions).
+
+### resolve()
+
+The `resolve($time, LoopInterface $loop)` function can be used to create a new Promise that
+resolves in `$time` seconds with the `$time` as the fulfillment value.
+
+```php
+Timer\resolve(1.5, $loop)->then(function ($time) {
+ echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;
+});
+```
+
+Internally, the given `$time` value will be used to start a timer that will
+resolve the promise once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+#### Resolve cancellation
+
+You can explicitly `cancel()` the resulting timer promise at any time:
+
+```php
+$timer = Timer\resolve(2.0, $loop);
+
+$timer->cancel();
+```
+
+This will abort the timer and *reject* with a `RuntimeException`.
+
+### reject()
+
+The `reject($time, LoopInterface $loop)` function can be used to create a new Promise
+which rejects in `$time` seconds with a `TimeoutException`.
+
+```php
+Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) {
+ echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL;
+});
+```
+
+Internally, the given `$time` value will be used to start a timer that will
+reject the promise once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+This function complements the [`resolve()`](#resolve) function
+and can be used as a basic building block for higher-level promise consumers.
+
+#### Reject cancellation
+
+You can explicitly `cancel()` the resulting timer promise at any time:
+
+```php
+$timer = Timer\reject(2.0, $loop);
+
+$timer->cancel();
+```
+
+This will abort the timer and *reject* with a `RuntimeException`.
+
+### TimeoutException
+
+The `TimeoutException` extends PHP's built-in `RuntimeException`.
+
+The `getTimeout()` method can be used to get the timeout value in seconds.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](http://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/promise-timer:^1.2.1
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/assets/php/vendor/react/promise-timer/composer.json b/assets/php/vendor/react/promise-timer/composer.json
new file mode 100644
index 0000000..e425dc6
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "react/promise-timer",
+ "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+ "keywords": ["Promise", "timeout", "timer", "event-loop", "ReactPHP", "async"],
+ "homepage": "https://github.com/react/promise-timer",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "React\\Promise\\Timer\\": "src/" },
+ "files": [ "src/functions.php" ]
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Promise\\Timer\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "~2.1|~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/assets/php/vendor/react/promise-timer/phpunit.xml.dist b/assets/php/vendor/react/promise-timer/phpunit.xml.dist
new file mode 100644
index 0000000..bb79fba
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/phpunit.xml.dist
@@ -0,0 +1,19 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
\ No newline at end of file
diff --git a/assets/php/vendor/react/promise-timer/src/TimeoutException.php b/assets/php/vendor/react/promise-timer/src/TimeoutException.php
new file mode 100644
index 0000000..18ea72f
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/src/TimeoutException.php
@@ -0,0 +1,22 @@
+timeout = $timeout;
+ }
+
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+}
diff --git a/assets/php/vendor/react/promise-timer/src/functions.php b/assets/php/vendor/react/promise-timer/src/functions.php
new file mode 100644
index 0000000..6ad9867
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/src/functions.php
@@ -0,0 +1,70 @@
+then(function ($v) use (&$timer, $loop, $resolve) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $resolve($v);
+ }, function ($v) use (&$timer, $loop, $reject) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $reject($v);
+ });
+
+ // promise already resolved => no need to start timer
+ if ($timer === false) {
+ return;
+ }
+
+ // start timeout timer which will cancel the input promise
+ $timer = $loop->addTimer($time, function () use ($time, $promise, $reject) {
+ $reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds'));
+
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ });
+ }, $canceller);
+}
+
+function resolve($time, LoopInterface $loop)
+{
+ return new Promise(function ($resolve) use ($loop, $time, &$timer) {
+ // resolve the promise when the timer fires in $time seconds
+ $timer = $loop->addTimer($time, function () use ($time, $resolve) {
+ $resolve($time);
+ });
+ }, function ($resolveUnused, $reject) use (&$timer, $loop) {
+ // cancelling this promise will cancel the timer and reject
+ $loop->cancelTimer($timer);
+ $reject(new \RuntimeException('Timer cancelled'));
+ });
+}
+
+function reject($time, LoopInterface $loop)
+{
+ return resolve($time, $loop)->then(function ($time) {
+ throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds');
+ });
+}
diff --git a/assets/php/vendor/react/promise-timer/tests/CallableStub.php b/assets/php/vendor/react/promise-timer/tests/CallableStub.php
new file mode 100644
index 0000000..a391aa5
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/tests/CallableStub.php
@@ -0,0 +1,10 @@
+loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseExpiredIsPendingWithoutRunningLoop()
+ {
+ $promise = Timer\reject(-1, $this->loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseWillBeRejectedOnTimeout()
+ {
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testPromiseExpiredWillBeRejectedOnTimeout()
+ {
+ $promise = Timer\reject(-1, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testCancelingPromiseWillRejectTimer()
+ {
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ }
+}
diff --git a/assets/php/vendor/react/promise-timer/tests/FunctionResolveTest.php b/assets/php/vendor/react/promise-timer/tests/FunctionResolveTest.php
new file mode 100644
index 0000000..0bfdc21
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/tests/FunctionResolveTest.php
@@ -0,0 +1,71 @@
+loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseExpiredIsPendingWithoutRunningLoop()
+ {
+ $promise = Timer\resolve(-1, $this->loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseWillBeResolvedOnTimeout()
+ {
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testPromiseExpiredWillBeResolvedOnTimeout()
+ {
+ $promise = Timer\resolve(-1, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testWillStartLoopTimer()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->with($this->equalTo(0.01));
+
+ Timer\resolve(0.01, $loop);
+ }
+
+ public function testCancellingPromiseWillCancelLoopTimer()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $timer = $this->getMockBuilder(interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
+
+ $promise = Timer\resolve(0.01, $loop);
+
+ $loop->expects($this->once())->method('cancelTimer')->with($this->equalTo($timer));
+
+ $promise->cancel();
+ }
+
+ public function testCancelingPromiseWillRejectTimer()
+ {
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ }
+}
diff --git a/assets/php/vendor/react/promise-timer/tests/FunctionTimeoutTest.php b/assets/php/vendor/react/promise-timer/tests/FunctionTimeoutTest.php
new file mode 100644
index 0000000..aaca2da
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/tests/FunctionTimeoutTest.php
@@ -0,0 +1,169 @@
+loop);
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testResolvedExpiredWillResolveRightAway()
+ {
+ $promise = Promise\resolve();
+
+ $promise = Timer\timeout($promise, -1, $this->loop);
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testResolvedWillNotStartTimer()
+ {
+ $promise = Promise\resolve();
+
+ Timer\timeout($promise, 3, $this->loop);
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.5, $time);
+ }
+
+ public function testRejectedWillRejectRightAway()
+ {
+ $promise = Promise\reject();
+
+ $promise = Timer\timeout($promise, 3, $this->loop);
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testRejectedWillNotStartTimer()
+ {
+ $promise = Promise\reject();
+
+ Timer\timeout($promise, 3, $this->loop);
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.5, $time);
+ }
+
+ public function testPendingWillRejectOnTimeout()
+ {
+ $promise = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testPendingCancellableWillBeCancelledOnTimeout()
+ {
+ $promise = $this->getMockBuilder('React\Promise\CancellablePromiseInterface')->getMock();
+ $promise->expects($this->once())->method('cancel');
+
+ Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ }
+
+ public function testCancelTimeoutWithoutCancellationhandlerWillNotCancelTimerAndWillNotReject()
+ {
+ $promise = new \React\Promise\Promise(function () { });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $timer = $this->getMockBuilder('React\EventLoop\Timer\TimerInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
+ $loop->expects($this->never())->method('cancelTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $timeout->cancel();
+
+ $this->expectPromisePending($timeout);
+ }
+
+ public function testResolvedPromiseWillNotStartTimer()
+ {
+ $promise = new \React\Promise\Promise(function ($resolve) { $resolve(true); });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $this->expectPromiseResolved($timeout);
+ }
+
+ public function testRejectedPromiseWillNotStartTimer()
+ {
+ $promise = Promise\reject(new \RuntimeException());
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillCancelGivenPromise()
+ {
+ $promise = new \React\Promise\Promise(function () { }, $this->expectCallableOnce());
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+ }
+
+ public function testCancelGivenPromiseWillReject()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillRejectIfGivenPromiseWillReject()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+
+ $this->expectPromiseRejected($promise);
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillResolveIfGivenPromiseWillResolve()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $resolve(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+
+ $this->expectPromiseResolved($promise);
+ $this->expectPromiseResolved($timeout);
+ }
+}
diff --git a/assets/php/vendor/react/promise-timer/tests/TestCase.php b/assets/php/vendor/react/promise-timer/tests/TestCase.php
new file mode 100644
index 0000000..9d8d49a
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/tests/TestCase.php
@@ -0,0 +1,61 @@
+loop = Factory::create();
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Promise\Timer\CallableStub')->getMock();
+ }
+
+ protected function expectPromiseRejected($promise)
+ {
+ return $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ protected function expectPromiseResolved($promise)
+ {
+ return $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+ }
+
+ protected function expectPromisePending($promise)
+ {
+ return $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+}
diff --git a/assets/php/vendor/react/promise-timer/tests/TimeoutExceptionTest.php b/assets/php/vendor/react/promise-timer/tests/TimeoutExceptionTest.php
new file mode 100644
index 0000000..e9bedd9
--- /dev/null
+++ b/assets/php/vendor/react/promise-timer/tests/TimeoutExceptionTest.php
@@ -0,0 +1,15 @@
+assertEquals(10, $e->getTimeout());
+ }
+}
diff --git a/assets/php/vendor/react/promise/.gitignore b/assets/php/vendor/react/promise/.gitignore
new file mode 100644
index 0000000..5241c60
--- /dev/null
+++ b/assets/php/vendor/react/promise/.gitignore
@@ -0,0 +1,5 @@
+composer.lock
+composer.phar
+phpunit.xml
+build/
+vendor/
diff --git a/assets/php/vendor/react/promise/.travis.yml b/assets/php/vendor/react/promise/.travis.yml
new file mode 100644
index 0000000..5d0c6ab
--- /dev/null
+++ b/assets/php/vendor/react/promise/.travis.yml
@@ -0,0 +1,22 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - nightly
+ - hhvm
+
+before_install:
+ - composer self-update
+
+install:
+ - composer install
+
+script:
+ - ./vendor/bin/phpunit -v --coverage-text --coverage-clover=./build/logs/clover.xml
+
+after_script:
+ - if [ -f ./build/logs/clover.xml ]; then travis_retry composer require satooshi/php-coveralls --no-interaction --update-with-dependencies; fi
+ - if [ -f ./build/logs/clover.xml ]; then php vendor/bin/coveralls -v; fi
diff --git a/assets/php/vendor/react/promise/CHANGELOG.md b/assets/php/vendor/react/promise/CHANGELOG.md
new file mode 100644
index 0000000..484e542
--- /dev/null
+++ b/assets/php/vendor/react/promise/CHANGELOG.md
@@ -0,0 +1,96 @@
+CHANGELOG for 2.x
+=================
+
+* 2.5.1 (2017-03-25)
+
+ * Fix circular references when resolving with a promise which follows
+ itself (#94).
+
+* 2.5.0 (2016-12-22)
+
+ * Revert automatic cancellation of pending collection promises once the
+ output promise resolves. This was introduced in 42d86b7 (PR #36, released
+ in [v2.3.0](https://github.com/reactphp/promise/releases/tag/v2.3.0)) and
+ was both unintended and backward incompatible.
+
+ If you need automatic cancellation, you can use something like:
+
+ ```php
+ function allAndCancel(array $promises)
+ {
+ return \React\Promise\all($promises)
+ ->always(function() use ($promises) {
+ foreach ($promises as $promise) {
+ if ($promise instanceof \React\Promise\CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ });
+ }
+ ```
+ * `all()` and `map()` functions now preserve the order of the array (#77).
+ * Fix circular references when resolving a promise with itself (#71).
+
+* 2.4.1 (2016-05-03)
+
+ * Fix `some()` not cancelling pending promises when too much input promises
+ reject (16ff799).
+
+* 2.4.0 (2016-03-31)
+
+ * Support foreign thenables in `resolve()`.
+ Any object that provides a `then()` method is now assimilated to a trusted
+ promise that follows the state of this thenable (#52).
+ * Fix `some()` and `any()` for input arrays containing not enough items
+ (#34).
+
+* 2.3.0 (2016-03-24)
+
+ * Allow cancellation of promises returned by functions working on promise
+ collections (#36).
+ * Handle `\Throwable` in the same way as `\Exception` (#51 by @joshdifabio).
+
+* 2.2.2 (2016-02-26)
+
+ * Fix cancellation handlers called multiple times (#47 by @clue).
+
+* 2.2.1 (2015-07-03)
+
+ * Fix stack error when resolving a promise in its own fulfillment or
+ rejection handlers.
+
+* 2.2.0 (2014-12-30)
+
+ * Introduce new `ExtendedPromiseInterface` implemented by all promises.
+ * Add new `done()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `otherwise()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `always()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `progress()` method (part of the `ExtendedPromiseInterface`).
+ * Rename `Deferred::progress` to `Deferred::notify` to avoid confusion with
+ `ExtendedPromiseInterface::progress` (a `Deferred::progress` alias is
+ still available for backward compatibility)
+ * `resolve()` now always returns a `ExtendedPromiseInterface`.
+
+* 2.1.0 (2014-10-15)
+
+ * Introduce new `CancellablePromiseInterface` implemented by all promises.
+ * Add new `cancel()` method (part of the `CancellablePromiseInterface`).
+
+* 2.0.0 (2013-12-10)
+
+ New major release. The goal is to streamline the API and to make it more
+ compliant with other promise libraries and especially with the new upcoming
+ [ES6 promises specification](https://github.com/domenic/promises-unwrapping/).
+
+ * Add standalone Promise class.
+ * Add new `race()` function.
+ * BC break: Bump minimum PHP version to PHP 5.4.
+ * BC break: Remove `ResolverInterface` and `PromiseInterface` from
+ `Deferred`.
+ * BC break: Change signature of `PromiseInterface`.
+ * BC break: Remove `When` and `Util` classes and move static methods to
+ functions.
+ * BC break: `FulfilledPromise` and `RejectedPromise` now throw an exception
+ when initialized with a promise instead of a value/reason.
+ * BC break: `Deferred::resolve()` and `Deferred::reject()` no longer return
+ a promise.
diff --git a/assets/php/vendor/react/promise/LICENSE b/assets/php/vendor/react/promise/LICENSE
new file mode 100644
index 0000000..5919d20
--- /dev/null
+++ b/assets/php/vendor/react/promise/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012-2016 Jan Sorgalla
+
+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.
diff --git a/assets/php/vendor/react/promise/README.md b/assets/php/vendor/react/promise/README.md
new file mode 100644
index 0000000..9c0558c
--- /dev/null
+++ b/assets/php/vendor/react/promise/README.md
@@ -0,0 +1,840 @@
+React/Promise
+=============
+
+A lightweight implementation of
+[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+[](http://travis-ci.org/reactphp/promise)
+[](https://coveralls.io/github/reactphp/promise?branch=master)
+
+Table of Contents
+-----------------
+
+1. [Introduction](#introduction)
+2. [Concepts](#concepts)
+ * [Deferred](#deferred)
+ * [Promise](#promise)
+3. [API](#api)
+ * [Deferred](#deferred-1)
+ * [Deferred::promise()](#deferredpromise)
+ * [Deferred::resolve()](#deferredresolve)
+ * [Deferred::reject()](#deferredreject)
+ * [Deferred::notify()](#deferrednotify)
+ * [PromiseInterface](#promiseinterface)
+ * [PromiseInterface::then()](#promiseinterfacethen)
+ * [ExtendedPromiseInterface](#extendedpromiseinterface)
+ * [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
+ * [ExtendedPromiseInterface::otherwise()](#extendedpromiseinterfaceotherwise)
+ * [ExtendedPromiseInterface::always()](#extendedpromiseinterfacealways)
+ * [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress)
+ * [CancellablePromiseInterface](#cancellablepromiseinterface)
+ * [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel)
+ * [Promise](#promise-1)
+ * [FulfilledPromise](#fulfilledpromise)
+ * [RejectedPromise](#rejectedpromise)
+ * [LazyPromise](#lazypromise)
+ * [Functions](#functions)
+ * [resolve()](#resolve)
+ * [reject()](#reject)
+ * [all()](#all)
+ * [race()](#race)
+ * [any()](#any)
+ * [some()](#some)
+ * [map()](#map)
+ * [reduce()](#reduce)
+ * [PromisorInterface](#promisorinterface)
+4. [Examples](#examples)
+ * [How to use Deferred](#how-to-use-deferred)
+ * [How promise forwarding works](#how-promise-forwarding-works)
+ * [Resolution forwarding](#resolution-forwarding)
+ * [Rejection forwarding](#rejection-forwarding)
+ * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
+ * [Progress event forwarding](#progress-event-forwarding)
+ * [done() vs. then()](#done-vs-then)
+5. [Credits](#credits)
+6. [License](#license)
+
+Introduction
+------------
+
+React/Promise is a library implementing
+[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+It also provides several other useful promise-related concepts, such as joining
+multiple promises and mapping and reducing collections of promises.
+
+If you've never heard about promises before,
+[read this first](https://gist.github.com/3889970).
+
+Concepts
+--------
+
+### Deferred
+
+A **Deferred** represents a computation or unit of work that may not have
+completed yet. Typically (but not always), that computation will be something
+that executes asynchronously and completes at some point in the future.
+
+### Promise
+
+While a deferred represents the computation itself, a **Promise** represents
+the result of that computation. Thus, each deferred has a promise that acts as
+a placeholder for its actual result.
+
+API
+---
+
+### Deferred
+
+A deferred represents an operation whose resolution is pending. It has separate
+promise and resolver parts.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$promise = $deferred->promise();
+
+$deferred->resolve(mixed $value = null);
+$deferred->reject(mixed $reason = null);
+$deferred->notify(mixed $update = null);
+```
+
+The `promise` method returns the promise of the deferred.
+
+The `resolve` and `reject` methods control the state of the deferred.
+
+The `notify` method is for progress notification.
+
+The constructor of the `Deferred` accepts an optional `$canceller` argument.
+See [Promise](#promise-1) for more information.
+
+#### Deferred::promise()
+
+```php
+$promise = $deferred->promise();
+```
+
+Returns the promise of the deferred, which you can hand out to others while
+keeping the authority to modify its state to yourself.
+
+#### Deferred::resolve()
+
+```php
+$deferred->resolve(mixed $value = null);
+```
+
+Resolves the promise returned by `promise()`. All consumers are notified by
+having `$onFulfilled` (which they registered via `$promise->then()`) called with
+`$value`.
+
+If `$value` itself is a promise, the promise will transition to the state of
+this promise once it is resolved.
+
+#### Deferred::reject()
+
+```php
+$deferred->reject(mixed $reason = null);
+```
+
+Rejects the promise returned by `promise()`, signalling that the deferred's
+computation failed.
+All consumers are notified by having `$onRejected` (which they registered via
+`$promise->then()`) called with `$reason`.
+
+If `$reason` itself is a promise, the promise will be rejected with the outcome
+of this promise regardless whether it fulfills or rejects.
+
+#### Deferred::notify()
+
+```php
+$deferred->notify(mixed $update = null);
+```
+
+Triggers progress notifications, to indicate to consumers that the computation
+is making progress toward its result.
+
+All consumers are notified by having `$onProgress` (which they registered via
+`$promise->then()`) called with `$update`.
+
+### PromiseInterface
+
+The promise interface provides the common interface for all promise
+implementations.
+
+A promise represents an eventual outcome, which is either fulfillment (success)
+and an associated value, or rejection (failure) and an associated reason.
+
+Once in the fulfilled or rejected state, a promise becomes immutable.
+Neither its state nor its result (or error) can be modified.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+#### PromiseInterface::then()
+
+```php
+$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+```
+
+Transforms a promise's value by applying a function to the promise's fulfillment
+or rejection value. Returns a new promise for the transformed result.
+
+The `then()` method registers new fulfilled, rejection and progress handlers
+with a promise (all parameters are optional):
+
+ * `$onFulfilled` will be invoked once the promise is fulfilled and passed
+ the result as the first argument.
+ * `$onRejected` will be invoked once the promise is rejected and passed the
+ reason as the first argument.
+ * `$onProgress` will be invoked whenever the producer of the promise
+ triggers progress notifications and passed a single argument (whatever it
+ wants) to indicate progress.
+
+It returns a new promise that will fulfill with the return value of either
+`$onFulfilled` or `$onRejected`, whichever is called, or will reject with
+the thrown exception if either throws.
+
+A promise makes the following guarantees about handlers registered in
+the same call to `then()`:
+
+ 1. Only one of `$onFulfilled` or `$onRejected` will be called,
+ never both.
+ 2. `$onFulfilled` and `$onRejected` will never be called more
+ than once.
+ 3. `$onProgress` may be called multiple times.
+
+#### See also
+
+* [resolve()](#resolve) - Creating a resolved promise
+* [reject()](#reject) - Creating a rejected promise
+* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
+* [done() vs. then()](#done-vs-then)
+
+### ExtendedPromiseInterface
+
+The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut
+and utility methods which are not part of the Promises/A specification.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+#### ExtendedPromiseInterface::done()
+
+```php
+$promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+```
+
+Consumes the promise's ultimate value if the promise fulfills, or handles the
+ultimate error.
+
+It will cause a fatal error if either `$onFulfilled` or `$onRejected` throw or
+return a rejected promise.
+
+Since the purpose of `done()` is consumption rather than transformation,
+`done()` always returns `null`.
+
+#### See also
+
+* [PromiseInterface::then()](#promiseinterfacethen)
+* [done() vs. then()](#done-vs-then)
+
+#### ExtendedPromiseInterface::otherwise()
+
+```php
+$promise->otherwise(callable $onRejected);
+```
+
+Registers a rejection handler for promise. It is a shortcut for:
+
+```php
+$promise->then(null, $onRejected);
+```
+
+Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
+only specific errors.
+
+```php
+$promise
+ ->otherwise(function (\RuntimeException $reason) {
+ // Only catch \RuntimeException instances
+ // All other types of errors will propagate automatically
+ })
+ ->otherwise(function ($reason) {
+ // Catch other errors
+ )};
+```
+
+#### ExtendedPromiseInterface::always()
+
+```php
+$newPromise = $promise->always(callable $onFulfilledOrRejected);
+```
+
+Allows you to execute "cleanup" type tasks in a promise chain.
+
+It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
+when the promise is either fulfilled or rejected.
+
+* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
+ `$newPromise` will fulfill with the same value as `$promise`.
+* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
+ rejected promise, `$newPromise` will reject with the thrown exception or
+ rejected promise's reason.
+* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
+ `$newPromise` will reject with the same reason as `$promise`.
+* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
+ rejected promise, `$newPromise` will reject with the thrown exception or
+ rejected promise's reason.
+
+`always()` behaves similarly to the synchronous finally statement. When combined
+with `otherwise()`, `always()` allows you to write code that is similar to the familiar
+synchronous catch/finally pair.
+
+Consider the following synchronous code:
+
+```php
+try {
+ return doSomething();
+} catch(\Exception $e) {
+ return handleError($e);
+} finally {
+ cleanup();
+}
+```
+
+Similar asynchronous code (with `doSomething()` that returns a promise) can be
+written:
+
+```php
+return doSomething()
+ ->otherwise('handleError')
+ ->always('cleanup');
+```
+
+#### ExtendedPromiseInterface::progress()
+
+```php
+$promise->progress(callable $onProgress);
+```
+
+Registers a handler for progress updates from promise. It is a shortcut for:
+
+```php
+$promise->then(null, null, $onProgress);
+```
+
+### CancellablePromiseInterface
+
+A cancellable promise provides a mechanism for consumers to notify the creator
+of the promise that they are not longer interested in the result of an
+operation.
+
+#### CancellablePromiseInterface::cancel()
+
+``` php
+$promise->cancel();
+```
+
+The `cancel()` method notifies the creator of the promise that there is no
+further interest in the results of the operation.
+
+Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
+a promise has no effect.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+### Promise
+
+Creates a promise whose state is controlled by the functions passed to
+`$resolver`.
+
+```php
+$resolver = function (callable $resolve, callable $reject, callable $notify) {
+ // Do some work, possibly asynchronously, and then
+ // resolve or reject. You can notify of progress events
+ // along the way if you want/need.
+
+ $resolve($awesomeResult);
+ // or $resolve($anotherPromise);
+ // or $reject($nastyError);
+ // or $notify($progressNotification);
+};
+
+$canceller = function (callable $resolve, callable $reject, callable $progress) {
+ // Cancel/abort any running operations like network connections, streams etc.
+
+ $reject(new \Exception('Promise cancelled'));
+};
+
+$promise = new React\Promise\Promise($resolver, $canceller);
+```
+
+The promise constructor receives a resolver function and an optional canceller
+function which both will be called with 3 arguments:
+
+ * `$resolve($value)` - Primary function that seals the fate of the
+ returned promise. Accepts either a non-promise value, or another promise.
+ When called with a non-promise value, fulfills promise with that value.
+ When called with another promise, e.g. `$resolve($otherPromise)`, promise's
+ fate will be equivalent to that of `$otherPromise`.
+ * `$reject($reason)` - Function that rejects the promise.
+ * `$notify($update)` - Function that issues progress events for the promise.
+
+If the resolver or canceller throw an exception, the promise will be rejected
+with that thrown exception as the rejection reason.
+
+The resolver function will be called immediately, the canceller function only
+once all consumers called the `cancel()` method of the promise.
+
+### FulfilledPromise
+
+Creates a already fulfilled promise.
+
+```php
+$promise = React\Promise\FulfilledPromise($value);
+```
+
+Note, that `$value` **cannot** be a promise. It's recommended to use
+[resolve()](#resolve) for creating resolved promises.
+
+### RejectedPromise
+
+Creates a already rejected promise.
+
+```php
+$promise = React\Promise\RejectedPromise($reason);
+```
+
+Note, that `$reason` **cannot** be a promise. It's recommended to use
+[reject()](#reject) for creating rejected promises.
+
+### LazyPromise
+
+Creates a promise which will be lazily initialized by `$factory` once a consumer
+calls the `then()` method.
+
+```php
+$factory = function () {
+ $deferred = new React\Promise\Deferred();
+
+ // Do some heavy stuff here and resolve the deferred once completed
+
+ return $deferred->promise();
+};
+
+$promise = React\Promise\LazyPromise($factory);
+
+// $factory will only be executed once we call then()
+$promise->then(function ($value) {
+});
+```
+
+### Functions
+
+Useful functions for creating, joining, mapping and reducing collections of
+promises.
+
+All functions working on promise collections (like `all()`, `race()`, `some()`
+etc.) support cancellation. This means, if you call `cancel()` on the returned
+promise, all promises in the collection are cancelled. If the collection itself
+is a promise which resolves to an array, this promise is also cancelled.
+
+#### resolve()
+
+```php
+$promise = React\Promise\resolve(mixed $promiseOrValue);
+```
+
+Creates a promise for the supplied `$promiseOrValue`.
+
+If `$promiseOrValue` is a value, it will be the resolution value of the
+returned promise.
+
+If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
+a trusted promise that follows the state of the thenable is returned.
+
+If `$promiseOrValue` is a promise, it will be returned as is.
+
+Note: The promise returned is always a promise implementing
+[ExtendedPromiseInterface](#extendedpromiseinterface). If you pass in a custom
+promise which only implements [PromiseInterface](#promiseinterface), this
+promise will be assimilated to a extended promise following `$promiseOrValue`.
+
+#### reject()
+
+```php
+$promise = React\Promise\reject(mixed $promiseOrValue);
+```
+
+Creates a rejected promise for the supplied `$promiseOrValue`.
+
+If `$promiseOrValue` is a value, it will be the rejection value of the
+returned promise.
+
+If `$promiseOrValue` is a promise, its completion value will be the rejected
+value of the returned promise.
+
+This can be useful in situations where you need to reject a promise without
+throwing an exception. For example, it allows you to propagate a rejection with
+the value of another promise.
+
+#### all()
+
+```php
+$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Returns a promise that will resolve only once all the items in
+`$promisesOrValues` have resolved. The resolution value of the returned promise
+will be an array containing the resolution values of each of the items in
+`$promisesOrValues`.
+
+#### race()
+
+```php
+$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Initiates a competitive race that allows one winner. Returns a promise which is
+resolved in the same way the first settled promise resolves.
+
+#### any()
+
+```php
+$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Returns a promise that will resolve when any one of the items in
+`$promisesOrValues` resolves. The resolution value of the returned promise
+will be the resolution value of the triggering item.
+
+The returned promise will only reject if *all* items in `$promisesOrValues` are
+rejected. The rejection value will be an array of all rejection reasons.
+
+The returned promise will also reject with a `React\Promise\Exception\LengthException`
+if `$promisesOrValues` contains 0 items.
+
+#### some()
+
+```php
+$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany);
+```
+
+Returns a promise that will resolve when `$howMany` of the supplied items in
+`$promisesOrValues` resolve. The resolution value of the returned promise
+will be an array of length `$howMany` containing the resolution values of the
+triggering items.
+
+The returned promise will reject if it becomes impossible for `$howMany` items
+to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
+reject). The rejection value will be an array of
+`(count($promisesOrValues) - $howMany) + 1` rejection reasons.
+
+The returned promise will also reject with a `React\Promise\Exception\LengthException`
+if `$promisesOrValues` contains less items than `$howMany`.
+
+#### map()
+
+```php
+$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc);
+```
+
+Traditional map function, similar to `array_map()`, but allows input to contain
+promises and/or values, and `$mapFunc` may return either a value or a promise.
+
+The map function receives each item as argument, where item is a fully resolved
+value of a promise or value in `$promisesOrValues`.
+
+#### reduce()
+
+```php
+$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null);
+```
+
+Traditional reduce function, similar to `array_reduce()`, but input may contain
+promises and/or values, and `$reduceFunc` may return either a value or a
+promise, *and* `$initialValue` may be a promise or a value for the starting
+value.
+
+### PromisorInterface
+
+The `React\Promise\PromisorInterface` provides a common interface for objects
+that provide a promise. `React\Promise\Deferred` implements it, but since it
+is part of the public API anyone can implement it.
+
+Examples
+--------
+
+### How to use Deferred
+
+```php
+function getAwesomeResultPromise()
+{
+ $deferred = new React\Promise\Deferred();
+
+ // Execute a Node.js-style function using the callback pattern
+ computeAwesomeResultAsynchronously(function ($error, $result) use ($deferred) {
+ if ($error) {
+ $deferred->reject($error);
+ } else {
+ $deferred->resolve($result);
+ }
+ });
+
+ // Return the promise
+ return $deferred->promise();
+}
+
+getAwesomeResultPromise()
+ ->then(
+ function ($value) {
+ // Deferred resolved, do something with $value
+ },
+ function ($reason) {
+ // Deferred rejected, do something with $reason
+ },
+ function ($update) {
+ // Progress notification triggered, do something with $update
+ }
+ );
+```
+
+### How promise forwarding works
+
+A few simple examples to show how the mechanics of Promises/A forwarding works.
+These examples are contrived, of course, and in real usage, promise chains will
+typically be spread across several function calls, or even several levels of
+your application architecture.
+
+#### Resolution forwarding
+
+Resolved promises forward resolution values to the next promise.
+The first promise, `$deferred->promise()`, will resolve with the value passed
+to `$deferred->resolve()` below.
+
+Each call to `then()` returns a new promise that will resolve with the return
+value of the previous handler. This creates a promise "pipeline".
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ // $x will be the value passed to $deferred->resolve() below
+ // and returns a *new promise* for $x + 1
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 2
+ // This handler receives the return value of the
+ // previous handler.
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 3
+ // This handler receives the return value of the
+ // previous handler.
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 4
+ // This handler receives the return value of the
+ // previous handler.
+ echo 'Resolve ' . $x;
+ });
+
+$deferred->resolve(1); // Prints "Resolve 4"
+```
+
+#### Rejection forwarding
+
+Rejected promises behave similarly, and also work similarly to try/catch:
+When you catch an exception, you must rethrow for it to propagate.
+
+Similarly, when you handle a rejected promise, to propagate the rejection,
+"rethrow" it by either returning a rejected promise, or actually throwing
+(since promise translates thrown exceptions into rejections)
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ throw new \Exception($x + 1);
+ })
+ ->otherwise(function (\Exception $x) {
+ // Propagate the rejection
+ throw $x;
+ })
+ ->otherwise(function (\Exception $x) {
+ // Can also propagate by returning another rejection
+ return React\Promise\reject(
+ new \Exception($x->getMessage() + 1)
+ );
+ })
+ ->otherwise(function ($x) {
+ echo 'Reject ' . $x->getMessage(); // 3
+ });
+
+$deferred->resolve(1); // Prints "Reject 3"
+```
+
+#### Mixed resolution and rejection forwarding
+
+Just like try/catch, you can choose to propagate or not. Mixing resolutions and
+rejections will still forward handler results in a predictable way.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ throw new \Exception($x + 1);
+ })
+ ->otherwise(function (\Exception $x) {
+ // Handle the rejection, and don't propagate.
+ // This is like catch without a rethrow
+ return $x->getMessage() + 1;
+ })
+ ->then(function ($x) {
+ echo 'Mixed ' . $x; // 4
+ });
+
+$deferred->resolve(1); // Prints "Mixed 4"
+```
+
+#### Progress event forwarding
+
+In the same way as resolution and rejection handlers, your progress handler
+**MUST** return a progress event to be propagated to the next link in the chain.
+If you return nothing, `null` will be propagated.
+
+Also in the same way as resolutions and rejections, if you don't register a
+progress handler, the update will be propagated through.
+
+If your progress handler throws an exception, the exception will be propagated
+to the next link in the chain. The best thing to do is to ensure your progress
+handlers do not throw exceptions.
+
+This gives you the opportunity to transform progress events at each step in the
+chain so that they are meaningful to the next step. It also allows you to choose
+not to transform them, and simply let them propagate untransformed, by not
+registering a progress handler.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->progress(function ($update) {
+ return $update + 1;
+ })
+ ->progress(function ($update) {
+ echo 'Progress ' . $update; // 2
+ });
+
+$deferred->notify(1); // Prints "Progress 2"
+```
+
+### done() vs. then()
+
+The golden rule is:
+
+ Either return your promise, or call done() on it.
+
+At a first glance, `then()` and `done()` seem very similar. However, there are
+important distinctions.
+
+The intent of `then()` is to transform a promise's value and to pass or return
+a new promise for the transformed value along to other parts of your code.
+
+The intent of `done()` is to consume a promise's value, transferring
+responsibility for the value to your code.
+
+In addition to transforming a value, `then()` allows you to recover from, or
+propagate intermediate errors. Any errors that are not handled will be caught
+by the promise machinery and used to reject the promise returned by `then()`.
+
+Calling `done()` transfers all responsibility for errors to your code. If an
+error (either a thrown exception or returned rejection) escapes the
+`$onFulfilled` or `$onRejected` callbacks you provide to done, it will be
+rethrown in an uncatchable way causing a fatal error.
+
+```php
+function getJsonResult()
+{
+ return queryApi()
+ ->then(
+ // Transform API results to an object
+ function ($jsonResultString) {
+ return json_decode($jsonResultString);
+ },
+ // Transform API errors to an exception
+ function ($jsonErrorString) {
+ $object = json_decode($jsonErrorString);
+ throw new ApiErrorException($object->errorMessage);
+ }
+ );
+}
+
+// Here we provide no rejection handler. If the promise returned has been
+// rejected, the ApiErrorException will be thrown
+getJsonResult()
+ ->done(
+ // Consume transformed object
+ function ($jsonResultObject) {
+ // Do something with $jsonResultObject
+ }
+ );
+
+// Here we provide a rejection handler which will either throw while debugging
+// or log the exception
+getJsonResult()
+ ->done(
+ function ($jsonResultObject) {
+ // Do something with $jsonResultObject
+ },
+ function (ApiErrorException $exception) {
+ if (isDebug()) {
+ throw $exception;
+ } else {
+ logException($exception);
+ }
+ }
+ );
+```
+
+Note that if a rejection value is not an instance of `\Exception`, it will be
+wrapped in an exception of the type `React\Promise\UnhandledRejectionException`.
+
+You can get the original rejection reason by calling `$exception->getReason()`.
+
+Credits
+-------
+
+React/Promise is a port of [when.js](https://github.com/cujojs/when)
+by [Brian Cavalier](https://github.com/briancavalier).
+
+Also, large parts of the documentation have been ported from the when.js
+[Wiki](https://github.com/cujojs/when/wiki) and the
+[API docs](https://github.com/cujojs/when/blob/master/docs/api.md).
+
+License
+-------
+
+React/Promise is released under the [MIT](https://github.com/reactphp/promise/blob/master/LICENSE) license.
diff --git a/assets/php/vendor/react/promise/composer.json b/assets/php/vendor/react/promise/composer.json
new file mode 100644
index 0000000..2fc4809
--- /dev/null
+++ b/assets/php/vendor/react/promise/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "react/promise",
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "license": "MIT",
+ "authors": [
+ {"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"}
+ ],
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Promise\\": "tests/fixtures"
+ }
+ },
+ "keywords": [
+ "promise",
+ "promises"
+ ]
+}
diff --git a/assets/php/vendor/react/promise/phpunit.xml.dist b/assets/php/vendor/react/promise/phpunit.xml.dist
new file mode 100644
index 0000000..b9a689d
--- /dev/null
+++ b/assets/php/vendor/react/promise/phpunit.xml.dist
@@ -0,0 +1,28 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+ ./src/functions_include.php
+
+
+
+
diff --git a/assets/php/vendor/react/promise/src/CancellablePromiseInterface.php b/assets/php/vendor/react/promise/src/CancellablePromiseInterface.php
new file mode 100644
index 0000000..896db2d
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/CancellablePromiseInterface.php
@@ -0,0 +1,11 @@
+started) {
+ return;
+ }
+
+ $this->started = true;
+ $this->drain();
+ }
+
+ public function enqueue($cancellable)
+ {
+ if (!method_exists($cancellable, 'then') || !method_exists($cancellable, 'cancel')) {
+ return;
+ }
+
+ $length = array_push($this->queue, $cancellable);
+
+ if ($this->started && 1 === $length) {
+ $this->drain();
+ }
+ }
+
+ private function drain()
+ {
+ for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
+ $cancellable = $this->queue[$i];
+
+ $exception = null;
+
+ try {
+ $cancellable->cancel();
+ } catch (\Throwable $exception) {
+ } catch (\Exception $exception) {
+ }
+
+ unset($this->queue[$i]);
+
+ if ($exception) {
+ throw $exception;
+ }
+ }
+
+ $this->queue = [];
+ }
+}
diff --git a/assets/php/vendor/react/promise/src/Deferred.php b/assets/php/vendor/react/promise/src/Deferred.php
new file mode 100644
index 0000000..f23980c
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/Deferred.php
@@ -0,0 +1,60 @@
+canceller = $canceller;
+ }
+
+ public function promise()
+ {
+ if (null === $this->promise) {
+ $this->promise = new Promise(function ($resolve, $reject, $notify) {
+ $this->resolveCallback = $resolve;
+ $this->rejectCallback = $reject;
+ $this->notifyCallback = $notify;
+ }, $this->canceller);
+ }
+
+ return $this->promise;
+ }
+
+ public function resolve($value = null)
+ {
+ $this->promise();
+
+ call_user_func($this->resolveCallback, $value);
+ }
+
+ public function reject($reason = null)
+ {
+ $this->promise();
+
+ call_user_func($this->rejectCallback, $reason);
+ }
+
+ public function notify($update = null)
+ {
+ $this->promise();
+
+ call_user_func($this->notifyCallback, $update);
+ }
+
+ /**
+ * @deprecated 2.2.0
+ * @see Deferred::notify()
+ */
+ public function progress($update = null)
+ {
+ $this->notify($update);
+ }
+}
diff --git a/assets/php/vendor/react/promise/src/Exception/LengthException.php b/assets/php/vendor/react/promise/src/Exception/LengthException.php
new file mode 100644
index 0000000..775c48d
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/Exception/LengthException.php
@@ -0,0 +1,7 @@
+value = $value;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return $this;
+ }
+
+ try {
+ return resolve($onFulfilled($this->value));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return;
+ }
+
+ $result = $onFulfilled($this->value);
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this;
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/assets/php/vendor/react/promise/src/LazyPromise.php b/assets/php/vendor/react/promise/src/LazyPromise.php
new file mode 100644
index 0000000..7e3a3d3
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/LazyPromise.php
@@ -0,0 +1,63 @@
+factory = $factory;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->promise()->otherwise($onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->promise()->always($onFulfilledOrRejected);
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->promise()->progress($onProgress);
+ }
+
+ public function cancel()
+ {
+ return $this->promise()->cancel();
+ }
+
+ /**
+ * @internal
+ * @see Promise::settle()
+ */
+ public function promise()
+ {
+ if (null === $this->promise) {
+ try {
+ $this->promise = resolve(call_user_func($this->factory));
+ } catch (\Throwable $exception) {
+ $this->promise = new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ $this->promise = new RejectedPromise($exception);
+ }
+ }
+
+ return $this->promise;
+ }
+}
diff --git a/assets/php/vendor/react/promise/src/Promise.php b/assets/php/vendor/react/promise/src/Promise.php
new file mode 100644
index 0000000..0261eb3
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/Promise.php
@@ -0,0 +1,216 @@
+canceller = $canceller;
+ $this->call($resolver);
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ if (null === $this->canceller) {
+ return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
+ }
+
+ $this->requiredCancelRequests++;
+
+ return new static($this->resolver($onFulfilled, $onRejected, $onProgress), function () {
+ if (++$this->cancelRequests < $this->requiredCancelRequests) {
+ return;
+ }
+
+ $this->cancel();
+ });
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ $this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
+ $promise
+ ->done($onFulfilled, $onRejected);
+ };
+
+ if ($onProgress) {
+ $this->progressHandlers[] = $onProgress;
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, function ($reason) use ($onRejected) {
+ if (!_checkTypehint($onRejected, $reason)) {
+ return new RejectedPromise($reason);
+ }
+
+ return $onRejected($reason);
+ });
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ }, function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->then(null, null, $onProgress);
+ }
+
+ public function cancel()
+ {
+ if (null === $this->canceller || null !== $this->result) {
+ return;
+ }
+
+ $canceller = $this->canceller;
+ $this->canceller = null;
+
+ $this->call($canceller);
+ }
+
+ private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
+ if ($onProgress) {
+ $progressHandler = function ($update) use ($notify, $onProgress) {
+ try {
+ $notify($onProgress($update));
+ } catch (\Throwable $e) {
+ $notify($e);
+ } catch (\Exception $e) {
+ $notify($e);
+ }
+ };
+ } else {
+ $progressHandler = $notify;
+ }
+
+ $this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
+ $promise
+ ->then($onFulfilled, $onRejected)
+ ->done($resolve, $reject, $progressHandler);
+ };
+
+ $this->progressHandlers[] = $progressHandler;
+ };
+ }
+
+ private function resolve($value = null)
+ {
+ if (null !== $this->result) {
+ return;
+ }
+
+ $this->settle(resolve($value));
+ }
+
+ private function reject($reason = null)
+ {
+ if (null !== $this->result) {
+ return;
+ }
+
+ $this->settle(reject($reason));
+ }
+
+ private function notify($update = null)
+ {
+ if (null !== $this->result) {
+ return;
+ }
+
+ foreach ($this->progressHandlers as $handler) {
+ $handler($update);
+ }
+ }
+
+ private function settle(ExtendedPromiseInterface $promise)
+ {
+ $promise = $this->unwrap($promise);
+
+ $handlers = $this->handlers;
+
+ $this->progressHandlers = $this->handlers = [];
+ $this->result = $promise;
+
+ foreach ($handlers as $handler) {
+ $handler($promise);
+ }
+ }
+
+ private function unwrap($promise)
+ {
+ $promise = $this->extract($promise);
+
+ while ($promise instanceof self && null !== $promise->result) {
+ $promise = $this->extract($promise->result);
+ }
+
+ return $promise;
+ }
+
+ private function extract($promise)
+ {
+ if ($promise instanceof LazyPromise) {
+ $promise = $promise->promise();
+ }
+
+ if ($promise === $this) {
+ return new RejectedPromise(
+ new \LogicException('Cannot resolve a promise with itself.')
+ );
+ }
+
+ return $promise;
+ }
+
+ private function call(callable $callback)
+ {
+ try {
+ $callback(
+ function ($value = null) {
+ $this->resolve($value);
+ },
+ function ($reason = null) {
+ $this->reject($reason);
+ },
+ function ($update = null) {
+ $this->notify($update);
+ }
+ );
+ } catch (\Throwable $e) {
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $this->reject($e);
+ }
+ }
+}
diff --git a/assets/php/vendor/react/promise/src/PromiseInterface.php b/assets/php/vendor/react/promise/src/PromiseInterface.php
new file mode 100644
index 0000000..d80d114
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/PromiseInterface.php
@@ -0,0 +1,11 @@
+reason = $reason;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ return $this;
+ }
+
+ try {
+ return resolve($onRejected($this->reason));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ throw UnhandledRejectionException::resolve($this->reason);
+ }
+
+ $result = $onRejected($this->reason);
+
+ if ($result instanceof self) {
+ throw UnhandledRejectionException::resolve($result->reason);
+ }
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ if (!_checkTypehint($onRejected, $this->reason)) {
+ return $this;
+ }
+
+ return $this->then(null, $onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(null, function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/assets/php/vendor/react/promise/src/UnhandledRejectionException.php b/assets/php/vendor/react/promise/src/UnhandledRejectionException.php
new file mode 100644
index 0000000..a44b7a1
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/UnhandledRejectionException.php
@@ -0,0 +1,31 @@
+reason = $reason;
+
+ $message = sprintf('Unhandled Rejection: %s', json_encode($reason));
+
+ parent::__construct($message, 0);
+ }
+
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/assets/php/vendor/react/promise/src/functions.php b/assets/php/vendor/react/promise/src/functions.php
new file mode 100644
index 0000000..70c0eb7
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/functions.php
@@ -0,0 +1,244 @@
+then($resolve, $reject, $notify);
+ }, $canceller);
+ }
+
+ return new FulfilledPromise($promiseOrValue);
+}
+
+function reject($promiseOrValue = null)
+{
+ if ($promiseOrValue instanceof PromiseInterface) {
+ return resolve($promiseOrValue)->then(function ($value) {
+ return new RejectedPromise($value);
+ });
+ }
+
+ return new RejectedPromise($promiseOrValue);
+}
+
+function all($promisesOrValues)
+{
+ return map($promisesOrValues, function ($val) {
+ return $val;
+ });
+}
+
+function race($promisesOrValues)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) {
+ if (!is_array($array) || !$array) {
+ $resolve();
+ return;
+ }
+
+ foreach ($array as $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($resolve, $reject, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function any($promisesOrValues)
+{
+ return some($promisesOrValues, 1)
+ ->then(function ($val) {
+ return array_shift($val);
+ });
+}
+
+function some($promisesOrValues, $howMany)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!is_array($array) || $howMany < 1) {
+ $resolve([]);
+ return;
+ }
+
+ $len = count($array);
+
+ if ($len < $howMany) {
+ throw new Exception\LengthException(
+ sprintf(
+ 'Input array must contain at least %d item%s but contains only %s item%s.',
+ $howMany,
+ 1 === $howMany ? '' : 's',
+ $len,
+ 1 === $len ? '' : 's'
+ )
+ );
+ }
+
+ $toResolve = $howMany;
+ $toReject = ($len - $toResolve) + 1;
+ $values = [];
+ $reasons = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $values[$i] = $val;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ };
+
+ $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $reasons[$i] = $reason;
+
+ if (0 === --$toReject) {
+ $reject($reasons);
+ }
+ };
+
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($fulfiller, $rejecter, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function map($promisesOrValues, callable $mapFunc)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!is_array($array) || !$array) {
+ $resolve([]);
+ return;
+ }
+
+ $toResolve = count($array);
+ $values = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+ $values[$i] = null;
+
+ resolve($promiseOrValue)
+ ->then($mapFunc)
+ ->done(
+ function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
+ $values[$i] = $mapped;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ },
+ $reject,
+ $notify
+ );
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!is_array($array)) {
+ $array = [];
+ }
+
+ $total = count($array);
+ $i = 0;
+
+ // Wrap the supplied $reduceFunc with one that handles promises and then
+ // delegates to the supplied.
+ $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
+ $cancellationQueue->enqueue($val);
+
+ return $current
+ ->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
+ return resolve($val)
+ ->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
+ return $reduceFunc($c, $value, $i++, $total);
+ });
+ });
+ };
+
+ $cancellationQueue->enqueue($initialValue);
+
+ array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
+ ->done($resolve, $reject, $notify);
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+// Internal functions
+function _checkTypehint(callable $callback, $object)
+{
+ if (!is_object($object)) {
+ return true;
+ }
+
+ if (is_array($callback)) {
+ $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (is_object($callback) && !$callback instanceof \Closure) {
+ $callbackReflection = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $callbackReflection = new \ReflectionFunction($callback);
+ }
+
+ $parameters = $callbackReflection->getParameters();
+
+ if (!isset($parameters[0])) {
+ return true;
+ }
+
+ $expectedException = $parameters[0];
+
+ if (!$expectedException->getClass()) {
+ return true;
+ }
+
+ return $expectedException->getClass()->isInstance($object);
+}
diff --git a/assets/php/vendor/react/promise/src/functions_include.php b/assets/php/vendor/react/promise/src/functions_include.php
new file mode 100644
index 0000000..c71decb
--- /dev/null
+++ b/assets/php/vendor/react/promise/src/functions_include.php
@@ -0,0 +1,5 @@
+enqueue($p);
+
+ $cancellationQueue();
+
+ $this->assertTrue($p->cancelCalled);
+ }
+
+ /** @test */
+ public function ignoresSimpleCancellable()
+ {
+ $p = new SimpleTestCancellable();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($p);
+
+ $cancellationQueue();
+
+ $this->assertFalse($p->cancelCalled);
+ }
+
+ /** @test */
+ public function callsCancelOnPromisesEnqueuedBeforeStart()
+ {
+ $d1 = $this->getCancellableDeferred();
+ $d2 = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($d1->promise());
+ $cancellationQueue->enqueue($d2->promise());
+
+ $cancellationQueue();
+ }
+
+ /** @test */
+ public function callsCancelOnPromisesEnqueuedAfterStart()
+ {
+ $d1 = $this->getCancellableDeferred();
+ $d2 = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+
+ $cancellationQueue();
+
+ $cancellationQueue->enqueue($d2->promise());
+ $cancellationQueue->enqueue($d1->promise());
+ }
+
+ /** @test */
+ public function doesNotCallCancelTwiceWhenStartedTwice()
+ {
+ $d = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($d->promise());
+
+ $cancellationQueue();
+ $cancellationQueue();
+ }
+
+ /** @test */
+ public function rethrowsExceptionsThrownFromCancel()
+ {
+ $this->setExpectedException('\Exception', 'test');
+
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel')
+ ->will($this->throwException(new \Exception('test')));
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($mock);
+
+ $cancellationQueue();
+ }
+
+ private function getCancellableDeferred()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return new Deferred($mock);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/DeferredTest.php b/assets/php/vendor/react/promise/tests/DeferredTest.php
new file mode 100644
index 0000000..16212e9
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/DeferredTest.php
@@ -0,0 +1,42 @@
+ [$d, 'promise'],
+ 'resolve' => [$d, 'resolve'],
+ 'reject' => [$d, 'reject'],
+ 'notify' => [$d, 'progress'],
+ 'settle' => [$d, 'resolve'],
+ ]);
+ }
+
+ /** @test */
+ public function progressIsAnAliasForNotify()
+ {
+ $deferred = new Deferred();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $deferred->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $deferred->progress($sentinel);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FulfilledPromiseTest.php b/assets/php/vendor/react/promise/tests/FulfilledPromiseTest.php
new file mode 100644
index 0000000..97fc8f6
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FulfilledPromiseTest.php
@@ -0,0 +1,50 @@
+ function () use (&$promise) {
+ if (!$promise) {
+ throw new \LogicException('FulfilledPromise must be resolved before obtaining the promise');
+ }
+
+ return $promise;
+ },
+ 'resolve' => function ($value = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new FulfilledPromise($value);
+ }
+ },
+ 'reject' => function () {
+ throw new \LogicException('You cannot call reject() for React\Promise\FulfilledPromise');
+ },
+ 'notify' => function () {
+ // no-op
+ },
+ 'settle' => function ($value = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new FulfilledPromise($value);
+ }
+ },
+ ]);
+ }
+
+ /** @test */
+ public function shouldThrowExceptionIfConstructedWithAPromise()
+ {
+ $this->setExpectedException('\InvalidArgumentException');
+
+ return new FulfilledPromise(new FulfilledPromise());
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionAllTest.php b/assets/php/vendor/react/promise/tests/FunctionAllTest.php
new file mode 100644
index 0000000..74c1d7c
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionAllTest.php
@@ -0,0 +1,114 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ all([])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all([1, 2, 3])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all([resolve(1), resolve(2), resolve(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([null, 1, null, 1, 1]));
+
+ all([null, 1, null, 1, 1])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfAnyInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ all([resolve(1), reject(2), resolve(3)])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all(resolve([1, 2, 3]))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ all(resolve(1))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ $deferred = new Deferred();
+
+ all([resolve(1), $deferred->promise(), resolve(3)])
+ ->then($mock);
+
+ $deferred->resolve(2);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionAnyTest.php b/assets/php/vendor/react/promise/tests/FunctionAnyTest.php
new file mode 100644
index 0000000..140b551
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionAnyTest.php
@@ -0,0 +1,204 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage();
+ })
+ );
+
+ any([])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullWithNonArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(null)
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithAnInputValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([1, 2, 3])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithAPromisedInputValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([resolve(1), resolve(2), resolve(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3]));
+
+ any([reject(1), reject(2), reject(3)])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWhenFirstInputPromiseResolves()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([resolve(1), reject(2), reject(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any(resolve([1, 2, 3]))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(resolve(1))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldNotRelyOnArryIndexesWhenUnwrappingToASingleResolutionValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+
+ any(['abc' => $d1->promise(), 1 => $d2->promise()])
+ ->then($mock);
+
+ $d2->resolve(2);
+ $d1->resolve(1);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(reject())
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ any($mock)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ any([$mock1, $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 1)->cancel();
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionCheckTypehintTest.php b/assets/php/vendor/react/promise/tests/FunctionCheckTypehintTest.php
new file mode 100644
index 0000000..8449bc1
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionCheckTypehintTest.php
@@ -0,0 +1,118 @@
+assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptFunctionStringCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithTypehint', new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint('React\Promise\testCallbackWithTypehint', new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptInvokableObjectCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint(new TestCallbackWithTypehintClass(), new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(new TestCallbackWithTypehintClass(), new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptObjectMethodCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptStaticClassCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptClosureCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptFunctionStringCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithoutTypehint', new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptInvokableObjectCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(new TestCallbackWithoutTypehintClass(), new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptObjectMethodCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint([new TestCallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptStaticClassCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
+ }
+}
+
+function testCallbackWithTypehint(\InvalidArgumentException $e)
+{
+}
+
+function testCallbackWithoutTypehint()
+{
+}
+
+class TestCallbackWithTypehintClass
+{
+ public function __invoke(\InvalidArgumentException $e)
+ {
+
+ }
+
+ public function testCallback(\InvalidArgumentException $e)
+ {
+
+ }
+
+ public static function testCallbackStatic(\InvalidArgumentException $e)
+ {
+
+ }
+}
+
+class TestCallbackWithoutTypehintClass
+{
+ public function __invoke()
+ {
+
+ }
+
+ public function testCallback()
+ {
+
+ }
+
+ public static function testCallbackStatic()
+ {
+
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionMapTest.php b/assets/php/vendor/react/promise/tests/FunctionMapTest.php
new file mode 100644
index 0000000..1ea560a
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionMapTest.php
@@ -0,0 +1,198 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, 2, 3],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapInputPromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapMixedInputArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, resolve(2), 3],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapInputWhenMapperReturnsAPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, 2, 3],
+ $this->promiseMapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ resolve([1, resolve(2), 3]),
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ map(
+ resolve(1),
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ $deferred = new Deferred();
+
+ map(
+ [resolve(1), $deferred->promise(), resolve(3)],
+ $this->mapper()
+ )->then($mock);
+
+ $deferred->resolve(2);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputContainsRejection()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ map(
+ [resolve(1), reject(2), resolve(3)],
+ $this->mapper()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ map(
+ reject(),
+ $this->mapper()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ map(
+ $mock,
+ $this->mapper()
+ )->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ map(
+ [$mock1, $mock2],
+ $this->mapper()
+ )->cancel();
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionRaceTest.php b/assets/php/vendor/react/promise/tests/FunctionRaceTest.php
new file mode 100644
index 0000000..83770ec
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionRaceTest.php
@@ -0,0 +1,211 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ []
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ race(
+ [1, 2, 3]
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ race(
+ [$d1->promise(), $d2->promise(), $d3->promise()]
+ )->then($mock);
+
+ $d2->resolve(2);
+
+ $d1->resolve(1);
+ $d3->resolve(3);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ [null, 1, null, 2, 3]
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfFirstSettledPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ race(
+ [$d1->promise(), $d2->promise(), $d3->promise()]
+ )->then($this->expectCallableNever(), $mock);
+
+ $d2->reject(2);
+
+ $d1->resolve(1);
+ $d3->resolve(3);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ race(
+ resolve([1, 2, 3])
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ reject()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ race($mock)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ race([$mock1, $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ race([$deferred->promise(), $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->reject();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ race([$deferred->promise(), $mock2])->cancel();
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionReduceTest.php b/assets/php/vendor/react/promise/tests/FunctionReduceTest.php
new file mode 100644
index 0000000..8b43a87
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionReduceTest.php
@@ -0,0 +1,347 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(6));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceValuesWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceValuesWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithoutInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(6));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceEmptyInputWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ [],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceEmptyInputWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ [],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputContainsRejection()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ reduce(
+ [resolve(1), reject(2), resolve(3)],
+ $this->plus(),
+ resolve(1)
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithNullWhenInputIsEmptyAndNoInitialValueOrPromiseProvided()
+ {
+ // Note: this is different from when.js's behavior!
+ // In when.reduce(), this rejects with a TypeError exception (following
+ // JavaScript's [].reduce behavior.
+ // We're following PHP's array_reduce behavior and resolve with NULL.
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ reduce(
+ [],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAllowSparseArrayInputWithoutInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(3));
+
+ reduce(
+ [null, null, 1, null, 1, 1],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAllowSparseArrayInputWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(4));
+
+ reduce(
+ [null, null, 1, null, 1, 1],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceInInputOrder()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('123'));
+
+ reduce(
+ [1, 2, 3],
+ $this->append(),
+ ''
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('123'));
+
+ reduce(
+ resolve([1, 2, 3]),
+ $this->append(),
+ ''
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToInitialValueWhenInputPromiseDoesNotResolveToAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ resolve(1),
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldProvideCorrectBasisValue()
+ {
+ $insertIntoArray = function ($arr, $val, $i) {
+ $arr[$i] = $val;
+
+ return $arr;
+ };
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ reduce(
+ [$d1->promise(), $d2->promise(), $d3->promise()],
+ $insertIntoArray,
+ []
+ )->then($mock);
+
+ $d3->resolve(3);
+ $d1->resolve(1);
+ $d2->resolve(2);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ reduce(
+ reject(),
+ $this->plus(),
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ reduce(
+ $mock,
+ $this->plus(),
+ 1
+ )->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ reduce(
+ [$mock1, $mock2],
+ $this->plus(),
+ 1
+ )->cancel();
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionRejectTest.php b/assets/php/vendor/react/promise/tests/FunctionRejectTest.php
new file mode 100644
index 0000000..84b8ec6
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionRejectTest.php
@@ -0,0 +1,64 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($expected)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldRejectAFulfilledPromise()
+ {
+ $expected = 123;
+
+ $resolved = new FulfilledPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldRejectARejectedPromise()
+ {
+ $expected = 123;
+
+ $resolved = new RejectedPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionResolveTest.php b/assets/php/vendor/react/promise/tests/FunctionResolveTest.php
new file mode 100644
index 0000000..53126bc
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionResolveTest.php
@@ -0,0 +1,171 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($expected)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveAFulfilledPromise()
+ {
+ $expected = 123;
+
+ $resolved = new FulfilledPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($resolved)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveAThenable()
+ {
+ $thenable = new SimpleFulfilledTestThenable();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ resolve($thenable)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveACancellableThenable()
+ {
+ $thenable = new SimpleTestCancellableThenable();
+
+ $promise = resolve($thenable);
+ $promise->cancel();
+
+ $this->assertTrue($thenable->cancelCalled);
+ }
+
+ /** @test */
+ public function shouldRejectARejectedPromise()
+ {
+ $expected = 123;
+
+ $resolved = new RejectedPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldSupportDeepNestingInPromiseChains()
+ {
+ $d = new Deferred();
+ $d->resolve(false);
+
+ $result = resolve(resolve($d->promise()->then(function ($val) {
+ $d = new Deferred();
+ $d->resolve($val);
+
+ $identity = function ($val) {
+ return $val;
+ };
+
+ return resolve($d->promise()->then($identity))->then(
+ function ($val) {
+ return !$val;
+ }
+ );
+ })));
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $result->then($mock);
+ }
+
+ /** @test */
+ public function shouldSupportVeryDeepNestedPromises()
+ {
+ $deferreds = [];
+
+ // @TODO Increase count once global-queue is merged
+ for ($i = 0; $i < 10; $i++) {
+ $deferreds[] = $d = new Deferred();
+ $p = $d->promise();
+
+ $last = $p;
+ for ($j = 0; $j < 10; $j++) {
+ $last = $last->then(function($result) {
+ return $result;
+ });
+ }
+ }
+
+ $p = null;
+ foreach ($deferreds as $d) {
+ if ($p) {
+ $d->resolve($p);
+ }
+
+ $p = $d->promise();
+ }
+
+ $deferreds[0]->resolve(true);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $deferreds[0]->promise()->then($mock);
+ }
+
+ /** @test */
+ public function returnsExtendePromiseForSimplePromise()
+ {
+ $promise = $this
+ ->getMockBuilder('React\Promise\PromiseInterface')
+ ->getMock();
+
+ $this->assertInstanceOf('React\Promise\ExtendedPromiseInterface', resolve($promise));
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/FunctionSomeTest.php b/assets/php/vendor/react/promise/tests/FunctionSomeTest.php
new file mode 100644
index 0000000..276b54b
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/FunctionSomeTest.php
@@ -0,0 +1,258 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage();
+ })
+ );
+
+ some(
+ [],
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldRejectWithLengthExceptionWithInputArrayContainingNotEnoughItems()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 4 items but contains only 3 items.' === $exception->getMessage();
+ })
+ );
+
+ some(
+ [1, 2, 3],
+ 4
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWithNonArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ null,
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ [1, 2, 3],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ [resolve(1), resolve(2), resolve(3)],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([null, 1]));
+
+ some(
+ [null, 1, null, 2, 3],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfAnyInputPromiseRejectsBeforeDesiredNumberOfInputsAreResolved()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1 => 2, 2 => 3]));
+
+ some(
+ [resolve(1), reject(2), reject(3)],
+ 2
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ resolve([1, 2, 3]),
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithEmptyArrayIfHowManyIsLessThanOne()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ [1],
+ 0
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ resolve(1),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ some(
+ reject(),
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ some($mock, 1)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ some([$mock1, $mock2], 1)->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesFulfill()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 1);
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesReject()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->reject();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 2);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/LazyPromiseTest.php b/assets/php/vendor/react/promise/tests/LazyPromiseTest.php
new file mode 100644
index 0000000..b630881
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/LazyPromiseTest.php
@@ -0,0 +1,107 @@
+promise();
+ };
+
+ return new CallbackPromiseAdapter([
+ 'promise' => function () use ($factory) {
+ return new LazyPromise($factory);
+ },
+ 'resolve' => [$d, 'resolve'],
+ 'reject' => [$d, 'reject'],
+ 'notify' => [$d, 'progress'],
+ 'settle' => [$d, 'resolve'],
+ ]);
+ }
+
+ /** @test */
+ public function shouldNotCallFactoryIfThenIsNotInvoked()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->never())
+ ->method('__invoke');
+
+ new LazyPromise($factory);
+ }
+
+ /** @test */
+ public function shouldCallFactoryIfThenIsInvoked()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke');
+
+ $p = new LazyPromise($factory);
+ $p->then();
+ }
+
+ /** @test */
+ public function shouldReturnPromiseFromFactory()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue(new FulfilledPromise(1)));
+
+ $onFulfilled = $this->createCallableMock();
+ $onFulfilled
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $p = new LazyPromise($factory);
+
+ $p->then($onFulfilled);
+ }
+
+ /** @test */
+ public function shouldReturnPromiseIfFactoryReturnsNull()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue(null));
+
+ $p = new LazyPromise($factory);
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then());
+ }
+
+ /** @test */
+ public function shouldReturnRejectedPromiseIfFactoryThrowsException()
+ {
+ $exception = new \Exception();
+
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $onRejected = $this->createCallableMock();
+ $onRejected
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $p = new LazyPromise($factory);
+
+ $p->then($this->expectCallableNever(), $onRejected);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php b/assets/php/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php
new file mode 100644
index 0000000..bdedf46
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php
@@ -0,0 +1,40 @@
+callbacks = $callbacks;
+ }
+
+ public function promise()
+ {
+ return call_user_func_array($this->callbacks['promise'], func_get_args());
+ }
+
+ public function resolve()
+ {
+ return call_user_func_array($this->callbacks['resolve'], func_get_args());
+ }
+
+ public function reject()
+ {
+ return call_user_func_array($this->callbacks['reject'], func_get_args());
+ }
+
+ public function notify()
+ {
+ return call_user_func_array($this->callbacks['notify'], func_get_args());
+ }
+
+ public function settle()
+ {
+ return call_user_func_array($this->callbacks['settle'], func_get_args());
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php b/assets/php/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php
new file mode 100644
index 0000000..9157cd4
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php
@@ -0,0 +1,14 @@
+ function () use ($promise) {
+ return $promise;
+ },
+ 'resolve' => $resolveCallback,
+ 'reject' => $rejectCallback,
+ 'notify' => $progressCallback,
+ 'settle' => $resolveCallback,
+ ]);
+ }
+
+ /** @test */
+ public function shouldRejectIfResolverThrowsException()
+ {
+ $exception = new \Exception('foo');
+
+ $promise = new Promise(function () use ($exception) {
+ throw $exception;
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $promise
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldFulfillIfFullfilledWithSimplePromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(new SimpleFulfilledTestPromise());
+ }
+
+ /** @test */
+ public function shouldRejectIfRejectedWithSimplePromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->resolve(new SimpleRejectedTestPromise());
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php
new file mode 100644
index 0000000..d722d75
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php
@@ -0,0 +1,231 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isType('callable'), $this->isType('callable'), $this->isType('callable'));
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldFulfillPromiseIfCancellerFulfills()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve) {
+ $resolve(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock, $this->expectCallableNever());
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldRejectPromiseIfCancellerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) {
+ $reject(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldRejectPromiseWithExceptionIfCancellerThrows()
+ {
+ $e = new \Exception();
+
+ $adapter = $this->getPromiseTestAdapter(function () use ($e) {
+ throw $e;
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($e));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldProgressPromiseIfCancellerNotifies()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $progress) {
+ $progress(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerOnlyOnceIfCancellerResolves()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnCallback(function ($resolve) {
+ $resolve();
+ }));
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectIfCancellerDoesNothing()
+ {
+ $adapter = $this->getPromiseTestAdapter(function () {});
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever());
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerFromDeepNestedPromiseChain()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $promise = $adapter->promise()
+ ->then(function () {
+ return new Promise\Promise(function () {});
+ })
+ ->then(function () {
+ $d = new Promise\Deferred();
+
+ return $d->promise();
+ })
+ ->then(function () {
+ return new Promise\Promise(function () {});
+ });
+
+ $promise->cancel();
+ }
+
+ /** @test */
+ public function cancelCalledOnChildrenSouldOnlyCancelWhenAllChildrenCancelled()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldTriggerCancellerWhenAllChildrenCancel()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $child2 = $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ $child2->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldNotTriggerCancellerWhenCancellingOneChildrenMultipleTimes()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $child2 = $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ $child1->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldTriggerCancellerOnlyOnceWhenCancellingMultipleTimes()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $adapter->promise()
+ ->then()
+ ->then();
+
+ $adapter->promise()
+ ->then();
+
+ $adapter->promise()->cancel();
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/FullTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/FullTestTrait.php
new file mode 100644
index 0000000..3ce45d6
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseTest/FullTestTrait.php
@@ -0,0 +1,15 @@
+getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $adapter->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateProgressToDownstreamPromises()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnArgument(0));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateTransformedProgressToDownstreamPromises()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue($sentinel));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateCaughtExceptionValueAsProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $promise2 = $adapter2->promise();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ // resolve BEFORE attaching progress handler
+ $adapter->resolve();
+
+ $adapter->promise()
+ ->then(function () use ($promise2) {
+ return $promise2;
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $promise2 = $adapter2->promise();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(function () use ($promise2) {
+ return $promise2;
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ // resolve AFTER attaching progress handler
+ $adapter->resolve();
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressWhenResolvedWithAnotherPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue($sentinel));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->resolve($adapter2->promise());
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldAllowResolveAfterProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->at(0))
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+ $mock
+ ->expects($this->at(1))
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->notify(1);
+ $adapter->resolve(2);
+ }
+
+ /** @test */
+ public function notifyShouldAllowRejectAfterProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->at(0))
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+ $mock
+ ->expects($this->at(1))
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock,
+ $mock
+ );
+
+ $adapter->notify(1);
+ $adapter->reject(2);
+ }
+
+ /** @test */
+ public function notifyShouldReturnSilentlyOnProgressWhenAlreadyRejected()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(1);
+
+ $this->assertNull($adapter->notify());
+ }
+
+ /** @test */
+ public function notifyShouldInvokeProgressHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()->progress($mock);
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldInvokeProgressHandlerFromDone()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done(null, null, $mock));
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldThrowExceptionThrownProgressHandlerFromDone()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->notify(1);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php
new file mode 100644
index 0000000..428230b
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php
@@ -0,0 +1,351 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $adapter->resolve(2);
+
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function fulfilledPromiseShouldInvokeNewlyAddedCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve(1);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock, $this->expectCallableNever());
+ }
+
+ /** @test */
+ public function thenShouldForwardResultWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ null,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldForwardCallbackResultToNextCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return $val + 1;
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldForwardPromisedCallbackResultValueToNextCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return \React\Promise\resolve($val + 1);
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return \React\Promise\reject($val + 1);
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackThrows()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock2
+ );
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->resolve();
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function doneShouldInvokeFulfillmentHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done($mock));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownFulfillmentHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done(function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejectsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done(function () {
+ return \React\Promise\reject();
+ }));
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve(1);
+ $adapter->promise()->otherwise($this->expectCallableNever());
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {})
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromiseForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromiseForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php
new file mode 100644
index 0000000..a4f48ee
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php
@@ -0,0 +1,68 @@
+getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then());
+ }
+
+ /** @test */
+ public function thenShouldReturnAllowNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null));
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function doneShouldReturnNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function doneShouldReturnAllowNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done(null, null, null));
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $adapter->promise()->otherwise($this->expectCallableNever());
+ }
+
+ /** @test */
+ public function alwaysShouldReturnAPromiseForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php
new file mode 100644
index 0000000..98d1dcf
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php
@@ -0,0 +1,512 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $adapter->reject(2);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function rejectedPromiseShouldInvokeNewlyAddedCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(1);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldForwardUndefinedRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(null);
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function () {
+ // Presence of rejection handler is enough to switch back
+ // to resolve mode, even though it returns undefined.
+ // The ONLY way to propagate a rejection is to re-throw or
+ // return a rejected promise;
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return $val + 1;
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return \React\Promise\resolve($val + 1);
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldPropagateRejectionsWhenErrbackThrows()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock2
+ );
+ }
+
+ /** @test */
+ public function shouldPropagateRejectionsWhenErrbackReturnsARejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return \React\Promise\reject($val + 1);
+ }
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function doneShouldInvokeRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, $mock));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownByRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function unhandledRejectionExceptionThrownByDoneHoldsRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $expected = new \stdClass();
+
+ $adapter->reject($expected);
+
+ try {
+ $adapter->promise()->done();
+ } catch (UnhandledRejectionException $e) {
+ $this->assertSame($expected, $e->getReason());
+ return;
+ }
+
+ $this->fail();
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject();
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionProvidedAsRejectionValueForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function doneShouldThrowWithDeepNestingPromiseChainsForRejectedPromise()
+ {
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $exception = new \Exception('UnhandledRejectionException');
+
+ $d = new Deferred();
+ $d->resolve();
+
+ $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
+ $d = new Deferred();
+ $d->resolve();
+
+ return \React\Promise\resolve($d->promise()->then(function () {}))->then(
+ function () use ($exception) {
+ throw $exception;
+ }
+ );
+ })));
+
+ $result->done();
+ }
+
+ /** @test */
+ public function doneShouldRecoverWhenRejectionHandlerCatchesExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
+
+ }));
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $adapter->promise()->otherwise($mock);
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeNonTypeHintedRejectionHandlerIfReasonIsAnExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function ($reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeRejectionHandlerIfReasonMatchesTypehintForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \InvalidArgumentException();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerIfReaonsDoesNotMatchTypehintForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->expectCallableNever();
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {})
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromiseForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromiseForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception1 = new \Exception();
+ $exception2 = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception2));
+
+ $adapter->reject($exception1);
+ $adapter->promise()
+ ->always(function () use ($exception2) {
+ throw $exception2;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception1 = new \Exception();
+ $exception2 = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception2));
+
+ $adapter->reject($exception1);
+ $adapter->promise()
+ ->always(function () use ($exception2) {
+ return \React\Promise\reject($exception2);
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->reject();
+
+ $adapter->promise()->cancel();
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php
new file mode 100644
index 0000000..e363b6d
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php
@@ -0,0 +1,86 @@
+getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then());
+ }
+
+ /** @test */
+ public function thenShouldReturnAllowNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null));
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->settle();
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function doneShouldReturnNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertNull($adapter->promise()->done(null, function () {}));
+ }
+
+ /** @test */
+ public function doneShouldReturnAllowNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertNull($adapter->promise()->done(null, function () {}, null));
+ }
+
+ /** @test */
+ public function progressShouldNotInvokeProgressHandlerForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $adapter->promise()->progress($this->expectCallableNever());
+ $adapter->notify();
+ }
+
+ /** @test */
+ public function alwaysShouldReturnAPromiseForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php
new file mode 100644
index 0000000..063f178
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php
@@ -0,0 +1,368 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function rejectShouldRejectWithFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(Promise\resolve(1));
+ }
+
+ /** @test */
+ public function rejectShouldRejectWithRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(Promise\reject(1));
+ }
+
+ /** @test */
+ public function rejectShouldForwardReasonWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function rejectShouldMakePromiseImmutable()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(null, function ($value) use ($adapter) {
+ $adapter->reject(3);
+
+ return Promise\reject($value);
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->reject(1);
+ $adapter->reject(2);
+ }
+
+ /** @test */
+ public function notifyShouldInvokeOtherwiseHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->otherwise($mock);
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldInvokeRejectionHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done(null, $mock));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownByRejectionHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done());
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject();
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRetunsPendingPromiseWhichRejectsLater()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $d = new Deferred();
+ $promise = $d->promise();
+
+ $this->assertNull($adapter->promise()->done(null, function () use ($promise) {
+ return $promise;
+ }));
+ $adapter->reject(1);
+ $d->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionProvidedAsRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done());
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ }
+
+ /** @test */
+ public function doneShouldThrowWithDeepNestingPromiseChains()
+ {
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $exception = new \Exception('UnhandledRejectionException');
+
+ $d = new Deferred();
+
+ $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
+ $d = new Deferred();
+ $d->resolve();
+
+ return \React\Promise\resolve($d->promise()->then(function () {}))->then(
+ function () use ($exception) {
+ throw $exception;
+ }
+ );
+ })));
+
+ $result->done();
+
+ $d->resolve();
+ }
+
+ /** @test */
+ public function doneShouldRecoverWhenRejectionHandlerCatchesException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
+
+ }));
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {})
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php b/assets/php/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php
new file mode 100644
index 0000000..0736d35
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php
@@ -0,0 +1,312 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function resolveShouldResolveWithPromisedValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(Promise\resolve(1));
+ }
+
+ /** @test */
+ public function resolveShouldRejectWhenResolvedWithRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->resolve(Promise\reject(1));
+ }
+
+ /** @test */
+ public function resolveShouldForwardValueWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(
+ null,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function resolveShouldMakePromiseImmutable()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(function ($value) use ($adapter) {
+ $adapter->resolve(3);
+
+ return $value;
+ })
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+
+ $adapter->resolve(1);
+ $adapter->resolve(2);
+ }
+
+ /**
+ * @test
+ */
+ public function resolveShouldRejectWhenResolvedWithItself()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(new \LogicException('Cannot resolve a promise with itself.'));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->resolve($adapter->promise());
+ }
+
+ /**
+ * @test
+ */
+ public function resolveShouldRejectWhenResolvedWithAPromiseWhichFollowsItself()
+ {
+ $adapter1 = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(new \LogicException('Cannot resolve a promise with itself.'));
+
+ $promise1 = $adapter1->promise();
+
+ $promise2 = $adapter2->promise();
+
+ $promise2->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter1->resolve($promise2);
+ $adapter2->resolve($promise1);
+ }
+
+ /** @test */
+ public function doneShouldInvokeFulfillmentHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done($mock));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownFulfillmentHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(function () {
+ return \React\Promise\reject();
+ }));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {})
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForFulfillment()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForFulfillment()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+
+ $adapter->resolve(1);
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/RejectedPromiseTest.php b/assets/php/vendor/react/promise/tests/RejectedPromiseTest.php
new file mode 100644
index 0000000..c886b00
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/RejectedPromiseTest.php
@@ -0,0 +1,50 @@
+ function () use (&$promise) {
+ if (!$promise) {
+ throw new \LogicException('RejectedPromise must be rejected before obtaining the promise');
+ }
+
+ return $promise;
+ },
+ 'resolve' => function () {
+ throw new \LogicException('You cannot call resolve() for React\Promise\RejectedPromise');
+ },
+ 'reject' => function ($reason = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new RejectedPromise($reason);
+ }
+ },
+ 'notify' => function () {
+ // no-op
+ },
+ 'settle' => function ($reason = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new RejectedPromise($reason);
+ }
+ },
+ ]);
+ }
+
+ /** @test */
+ public function shouldThrowExceptionIfConstructedWithAPromise()
+ {
+ $this->setExpectedException('\InvalidArgumentException');
+
+ return new RejectedPromise(new RejectedPromise());
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/Stub/CallableStub.php b/assets/php/vendor/react/promise/tests/Stub/CallableStub.php
new file mode 100644
index 0000000..0120893
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/Stub/CallableStub.php
@@ -0,0 +1,10 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function createCallableMock()
+ {
+ return $this
+ ->getMockBuilder('React\\Promise\Stub\CallableStub')
+ ->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/bootstrap.php b/assets/php/vendor/react/promise/tests/bootstrap.php
new file mode 100644
index 0000000..9b7f872
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/bootstrap.php
@@ -0,0 +1,7 @@
+addPsr4('React\\Promise\\', __DIR__);
diff --git a/assets/php/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php b/assets/php/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php
new file mode 100644
index 0000000..ef4d530
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php
@@ -0,0 +1,21 @@
+cancelCalled = true;
+ }
+}
diff --git a/assets/php/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php b/assets/php/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php
new file mode 100644
index 0000000..c0f1593
--- /dev/null
+++ b/assets/php/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php
@@ -0,0 +1,18 @@
+cancelCalled = true;
+ }
+}
diff --git a/assets/php/vendor/react/socket/.gitignore b/assets/php/vendor/react/socket/.gitignore
new file mode 100644
index 0000000..987e2a2
--- /dev/null
+++ b/assets/php/vendor/react/socket/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/assets/php/vendor/react/socket/.travis.yml b/assets/php/vendor/react/socket/.travis.yml
new file mode 100644
index 0000000..917dc0c
--- /dev/null
+++ b/assets/php/vendor/react/socket/.travis.yml
@@ -0,0 +1,49 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+# - 7.0 # Mac OS X, ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ include:
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: hhvm
+ - os: osx
+
+sudo: false
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/assets/php/vendor/react/socket/CHANGELOG.md b/assets/php/vendor/react/socket/CHANGELOG.md
new file mode 100644
index 0000000..03c2eec
--- /dev/null
+++ b/assets/php/vendor/react/socket/CHANGELOG.md
@@ -0,0 +1,451 @@
+# Changelog
+
+## 0.8.10 (2018-02-28)
+
+* Feature: Update DNS dependency to support loading system default DNS
+ nameserver config on all supported platforms
+ (`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows)
+ (#152 by @clue)
+
+ This means that connecting to hosts that are managed by a local DNS server,
+ such as a corporate DNS server or when using Docker containers, will now
+ work as expected across all platforms with no changes required:
+
+ ```php
+ $connector = new Connector($loop);
+ $connector->connect('intranet.example:80')->then(function ($connection) {
+ // …
+ });
+ ```
+
+## 0.8.9 (2018-01-18)
+
+* Feature: Support explicitly choosing TLS version to negotiate with remote side
+ by respecting `crypto_method` context parameter for all classes.
+ (#149 by @clue)
+
+ By default, all connector and server classes support TLSv1.0+ and exclude
+ support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly
+ choose the TLS version you want to negotiate with the remote side:
+
+ ```php
+ // new: now supports 'crypto_method` context parameter for all classes
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+ )
+ ));
+ ```
+
+* Minor internal clean up to unify class imports
+ (#148 by @clue)
+
+## 0.8.8 (2018-01-06)
+
+* Improve test suite by adding test group to skip integration tests relying on
+ internet connection and fix minor documentation typo.
+ (#146 by @clue and #145 by @cn007b)
+
+## 0.8.7 (2017-12-24)
+
+* Fix: Fix closing socket resource before removing from loop
+ (#141 by @clue)
+
+ This fixes the root cause of an uncaught `Exception` that only manifested
+ itself after the recent Stream v0.7.4 component update and only if you're
+ using `ext-event` (`ExtEventLoop`).
+
+* Improve test suite by testing against PHP 7.2
+ (#140 by @carusogabriel)
+
+## 0.8.6 (2017-11-18)
+
+* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme
+ and add advanced `UnixServer` class.
+ (#120 by @andig)
+
+ ```php
+ // new: Server now supports "unix://" scheme
+ $server = new Server('unix:///tmp/server.sock', $loop);
+
+ // new: advanced usage
+ $server = new UnixServer('/tmp/server.sock', $loop);
+ ```
+
+* Restructure examples to ease getting started
+ (#136 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#133 by @gabriel-caruso and #134 by @clue)
+
+## 0.8.5 (2017-10-23)
+
+* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X
+ (#123 by @andig)
+
+* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed
+ (#129 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit v5 and
+ forward compatibility with upcoming EventLoop releases in tests and
+ test Mac OS X on Travis
+ (#122 by @andig and #125, #127 and #130 by @clue)
+
+* Readme improvements
+ (#118 by @jsor)
+
+## 0.8.4 (2017-09-16)
+
+* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead
+ (#117 by @clue)
+
+ This can be useful for consumers that do not support certain URIs, such as
+ when you want to explicitly connect to a Unix domain socket (UDS) path
+ instead of connecting to a default address assumed by an higher-level API:
+
+ ```php
+ $connector = new FixedUriConnector(
+ 'unix:///var/run/docker.sock',
+ new UnixConnector($loop)
+ );
+
+ // destination will be ignored, actually connects to Unix domain socket
+ $promise = $connector->connect('localhost:80');
+ ```
+
+## 0.8.3 (2017-09-08)
+
+* Feature: Reduce memory consumption for failed connections
+ (#113 by @valga)
+
+* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14
+ (#114 by @clue)
+
+## 0.8.2 (2017-08-25)
+
+* Feature: Update DNS dependency to support hosts file on all platforms
+ (#112 by @clue)
+
+ This means that connecting to hosts such as `localhost` will now work as
+ expected across all platforms with no changes required:
+
+ ```php
+ $connector = new Connector($loop);
+ $connector->connect('localhost:8080')->then(function ($connection) {
+ // …
+ });
+ ```
+
+## 0.8.1 (2017-08-15)
+
+* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and
+ target evenement 3.0 a long side 2.0 and 1.0
+ (#104 by @clue and #111 by @WyriHaximus)
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ fix HHVM build for now again and ignore future HHVM build errors
+ (#109 and #110 by @clue)
+
+* Minor documentation fixes
+ (#103 by @christiaan and #108 by @hansott)
+
+## 0.8.0 (2017-05-09)
+
+* Feature: New `Server` class now acts as a facade for existing server classes
+ and renamed old `Server` to `TcpServer` for advanced usage.
+ (#96 and #97 by @clue)
+
+ The `Server` class is now the main class in this package that implements the
+ `ServerInterface` and allows you to accept incoming streaming connections,
+ such as plaintext TCP/IP or secure TLS connection streams.
+
+ > This is not a BC break and consumer code does not have to be updated.
+
+* Feature / BC break: All addresses are now URIs that include the URI scheme
+ (#98 by @clue)
+
+ ```diff
+ - $parts = parse_url('tcp://' . $conn->getRemoteAddress());
+ + $parts = parse_url($conn->getRemoteAddress());
+ ```
+
+* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths
+ (#100 by @clue)
+
+* Feature: Forward compatibility with Stream v1.0 and v0.7
+ (#99 by @clue)
+
+## 0.7.2 (2017-04-24)
+
+* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs
+ (#94 by @clue)
+
+## 0.7.1 (2017-04-10)
+
+* Fix: Ignore HHVM errors when closing connection that is already closing
+ (#91 by @clue)
+
+## 0.7.0 (2017-04-10)
+
+* Feature: Merge SocketClient component into this component
+ (#87 by @clue)
+
+ This means that this package now provides async, streaming plaintext TCP/IP
+ and secure TLS socket server and client connections for ReactPHP.
+
+ ```
+ $connector = new React\Socket\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+ ```
+
+ Accordingly, the `ConnectionInterface` is now used to represent both incoming
+ server side connections as well as outgoing client side connections.
+
+ If you've previously used the SocketClient component to establish outgoing
+ client connections, upgrading should take no longer than a few minutes.
+ All classes have been merged as-is from the latest `v0.7.0` release with no
+ other changes, so you can simply update your code to use the updated namespace
+ like this:
+
+ ```php
+ // old from SocketClient component and namespace
+ $connector = new React\SocketClient\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+
+ // new
+ $connector = new React\Socket\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+ ```
+
+## 0.6.0 (2017-04-04)
+
+* Feature: Add `LimitingServer` to limit and keep track of open connections
+ (#86 by @clue)
+
+ ```php
+ $server = new Server(0, $loop);
+ $server = new LimitingServer($server, 100);
+
+ $server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+ });
+ ```
+
+* Feature / BC break: Add `pause()` and `resume()` methods to limit active
+ connections
+ (#84 by @clue)
+
+ ```php
+ $server = new Server(0, $loop);
+ $server->pause();
+
+ $loop->addTimer(1.0, function() use ($server) {
+ $server->resume();
+ });
+ ```
+
+## 0.5.1 (2017-03-09)
+
+* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6
+ (#79 by @clue)
+
+## 0.5.0 (2017-02-14)
+
+* Feature / BC break: Replace `listen()` call with URIs passed to constructor
+ and reject listening on hostnames with `InvalidArgumentException`
+ and replace `ConnectionException` with `RuntimeException` for consistency
+ (#61, #66 and #72 by @clue)
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080);
+
+ // new
+ $server = new Server(8080, $loop);
+ ```
+
+ Similarly, you can now pass a full listening URI to the constructor to change
+ the listening host:
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080, '127.0.0.1');
+
+ // new
+ $server = new Server('127.0.0.1:8080', $loop);
+ ```
+
+ Trying to start listening on (DNS) host names will now throw an
+ `InvalidArgumentException`, use IP addresses instead:
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080, 'localhost');
+
+ // new
+ $server = new Server('127.0.0.1:8080', $loop);
+ ```
+
+ If trying to listen fails (such as if port is already in use or port below
+ 1024 may require root access etc.), it will now throw a `RuntimeException`,
+ the `ConnectionException` class has been removed:
+
+ ```php
+ // old: throws React\Socket\ConnectionException
+ $server = new Server($loop);
+ $server->listen(80);
+
+ // new: throws RuntimeException
+ $server = new Server(80, $loop);
+ ```
+
+* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React
+ (#62 by @clue)
+
+ ```php
+ // old
+ $server->shutdown();
+
+ // new
+ $server->close();
+ ```
+
+* Feature / BC break: Replace `getPort()` with `getAddress()`
+ (#67 by @clue)
+
+ ```php
+ // old
+ echo $server->getPort(); // 8080
+
+ // new
+ echo $server->getAddress(); // 127.0.0.1:8080
+ ```
+
+* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP
+ (#65 by @clue)
+
+ ```php
+ // old
+ echo $connection->getRemoteAddress(); // 192.168.0.1
+
+ // new
+ echo $connection->getRemoteAddress(); // 192.168.0.1:51743
+ ```
+
+* Feature / BC break: Add `getLocalAddress()` method
+ (#68 by @clue)
+
+ ```php
+ echo $connection->getLocalAddress(); // 127.0.0.1:8080
+ ```
+
+* BC break: The `Server` and `SecureServer` class are now marked `final`
+ and you can no longer `extend` them
+ (which was never documented or recommended anyway).
+ Public properties and event handlers are now internal only.
+ Please use composition instead of extension.
+ (#71, #70 and #69 by @clue)
+
+## 0.4.6 (2017-01-26)
+
+* Feature: Support socket context options passed to `Server`
+ (#64 by @clue)
+
+* Fix: Properly return `null` for unknown addresses
+ (#63 by @clue)
+
+* Improve documentation for `ServerInterface` and lock test suite requirements
+ (#60 by @clue, #57 by @shaunbramley)
+
+## 0.4.5 (2017-01-08)
+
+* Feature: Add `SecureServer` for secure TLS connections
+ (#55 by @clue)
+
+* Add functional integration tests
+ (#54 by @clue)
+
+## 0.4.4 (2016-12-19)
+
+* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation
+ (#50 by @clue)
+
+* Feature / Fix: Improve test suite and switch to normal stream handler
+ (#51 by @clue)
+
+* Feature: Add examples
+ (#49 by @clue)
+
+## 0.4.3 (2016-03-01)
+
+* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing
+* Support for PHP7 and HHVM
+* Support PHP 5.3 again
+
+## 0.4.2 (2014-05-25)
+
+* Verify stream is a valid resource in Connection
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev)
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: Reset socket to non-blocking after shutting down (PHP bug)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+* Bump React dependencies to v0.4
+
+## 0.3.3 (2013-07-08)
+
+* Version bump
+
+## 0.3.2 (2013-05-10)
+
+* Version bump
+
+## 0.3.1 (2013-04-21)
+
+* Feature: Support binding to IPv6 addresses (@clue)
+
+## 0.3.0 (2013-04-14)
+
+* Bump React dependencies to v0.3
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.0 (2012-09-10)
+
+* Bump React dependencies to v0.2
+
+## 0.1.1 (2012-07-12)
+
+* Version bump
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/assets/php/vendor/react/socket/LICENSE b/assets/php/vendor/react/socket/LICENSE
new file mode 100644
index 0000000..a808108
--- /dev/null
+++ b/assets/php/vendor/react/socket/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+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.
diff --git a/assets/php/vendor/react/socket/README.md b/assets/php/vendor/react/socket/README.md
new file mode 100644
index 0000000..e8b53a0
--- /dev/null
+++ b/assets/php/vendor/react/socket/README.md
@@ -0,0 +1,1419 @@
+# Socket
+
+[](https://travis-ci.org/reactphp/socket)
+
+Async, streaming plaintext TCP/IP and secure TLS socket server and client
+connections for [ReactPHP](https://reactphp.org/).
+
+The socket library provides re-usable interfaces for a socket-layer
+server and client based on the [`EventLoop`](https://github.com/reactphp/event-loop)
+and [`Stream`](https://github.com/reactphp/stream) components.
+Its server component allows you to build networking servers that accept incoming
+connections from networking clients (such as an HTTP server).
+Its client component allows you to build networking clients that establish
+outgoing connections to networking servers (such as an HTTP or database client).
+This library provides async, streaming means for all of this, so you can
+handle multiple concurrent connections without blocking.
+
+**Table of Contents**
+
+* [Quickstart example](#quickstart-example)
+* [Connection usage](#connection-usage)
+ * [ConnectionInterface](#connectioninterface)
+ * [getRemoteAddress()](#getremoteaddress)
+ * [getLocalAddress()](#getlocaladdress)
+* [Server usage](#server-usage)
+ * [ServerInterface](#serverinterface)
+ * [connection event](#connection-event)
+ * [error event](#error-event)
+ * [getAddress()](#getaddress)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [close()](#close)
+ * [Server](#server)
+ * [Advanced server usage](#advanced-server-usage)
+ * [TcpServer](#tcpserver)
+ * [SecureServer](#secureserver)
+ * [UnixServer](#unixserver)
+ * [LimitingServer](#limitingserver)
+ * [getConnections()](#getconnections)
+* [Client usage](#client-usage)
+ * [ConnectorInterface](#connectorinterface)
+ * [connect()](#connect)
+ * [Connector](#connector)
+ * [Advanced client usage](#advanced-client-usage)
+ * [TcpConnector](#tcpconnector)
+ * [DnsConnector](#dnsconnector)
+ * [SecureConnector](#secureconnector)
+ * [TimeoutConnector](#timeoutconnector)
+ * [UnixConnector](#unixconnector)
+ * [FixUriConnector](#fixeduriconnector)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Quickstart example
+
+Here is a server that closes the connection if you send it anything:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
+
+$socket->on('connection', function (ConnectionInterface $conn) {
+ $conn->write("Hello " . $conn->getRemoteAddress() . "!\n");
+ $conn->write("Welcome to this amazing server!\n");
+ $conn->write("Here's a tip: don't say anything.\n");
+
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Here's a client that outputs the output of said server and then attempts to
+send it a string:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$connector = new React\Socket\Connector($loop);
+
+$connector->connect('127.0.0.1:8080')->then(function (ConnectionInterface $conn) use ($loop) {
+ $conn->pipe(new React\Stream\WritableResourceStream(STDOUT, $loop));
+ $conn->write("Hello World!\n");
+});
+
+$loop->run();
+```
+
+## Connection usage
+
+### ConnectionInterface
+
+The `ConnectionInterface` is used to represent any incoming and outgoing
+connection, such as a normal TCP/IP connection.
+
+An incoming or outgoing connection is a duplex stream (both readable and
+writable) that implements React's
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+It contains additional properties for the local and remote address (client IP)
+where this connection has been established to/from.
+
+Most commonly, instances implementing this `ConnectionInterface` are emitted
+by all classes implementing the [`ServerInterface`](#serverinterface) and
+used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
+
+Because the `ConnectionInterface` implements the underlying
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
+you can use any of its events and methods as usual:
+
+```php
+$connection->on('data', function ($chunk) {
+ echo $chunk;
+});
+
+$connection->on('end', function () {
+ echo 'ended';
+});
+
+$connection->on('error', function (Exception $e) {
+ echo 'error: ' . $e->getMessage();
+});
+
+$connection->on('close', function () {
+ echo 'closed';
+});
+
+$connection->write($data);
+$connection->end($data = null);
+$connection->close();
+// …
+```
+
+For more details, see the
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+
+#### getRemoteAddress()
+
+The `getRemoteAddress(): ?string` method returns the full remote address
+(URI) where this connection has been established with.
+
+```php
+$address = $connection->getRemoteAddress();
+echo 'Connection with ' . $address . PHP_EOL;
+```
+
+If the remote address can not be determined or is unknown at this time (such as
+after the connection has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+If this is a TCP/IP based connection and you only want the remote IP, you may
+use something like this:
+
+```php
+$address = $connection->getRemoteAddress();
+$ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+echo 'Connection with ' . $ip . PHP_EOL;
+```
+
+#### getLocalAddress()
+
+The `getLocalAddress(): ?string` method returns the full local address
+(URI) where this connection has been established with.
+
+```php
+$address = $connection->getLocalAddress();
+echo 'Connection with ' . $address . PHP_EOL;
+```
+
+If the local address can not be determined or is unknown at this time (such as
+after the connection has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+so they should not be confused.
+
+If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+the address `0.0.0.0`), you can use this method to find out which interface
+actually accepted this connection (such as a public or local interface).
+
+If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+you can use this method to find out which interface was actually
+used for this connection.
+
+## Server usage
+
+### ServerInterface
+
+The `ServerInterface` is responsible for providing an interface for accepting
+incoming streaming connections, such as a normal TCP/IP connection.
+
+Most higher-level components (such as a HTTP server) accept an instance
+implementing this interface to accept incoming streaming connections.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against any other implementation of this interface.
+This means that you SHOULD typehint against this interface instead of a concrete
+implementation of this interface.
+
+Besides defining a few methods, this interface also implements the
+[`EventEmitterInterface`](https://github.com/igorw/evenement)
+which allows you to react to certain events.
+
+#### connection event
+
+The `connection` event will be emitted whenever a new connection has been
+established, i.e. a new client connects to this server socket:
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'new connection' . PHP_EOL;
+});
+```
+
+See also the [`ConnectionInterface`](#connectioninterface) for more details
+about handling the incoming connection.
+
+#### error event
+
+The `error` event will be emitted whenever there's an error accepting a new
+connection from a client.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+Note that this is not a fatal error event, i.e. the server keeps listening for
+new connections even after this event.
+
+
+#### getAddress()
+
+The `getAddress(): ?string` method can be used to
+return the full address (URI) this server is currently listening on.
+
+```php
+$address = $server->getAddress();
+echo 'Server listening on ' . $address . PHP_EOL;
+```
+
+If the address can not be determined or is unknown at this time (such as
+after the socket has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+If this is a TCP/IP based server and you only want the local port, you may
+use something like this:
+
+```php
+$address = $server->getAddress();
+$port = parse_url($address, PHP_URL_PORT);
+echo 'Server listening on port ' . $port . PHP_EOL;
+```
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause accepting new incoming connections.
+
+Removes the socket resource from the EventLoop and thus stop accepting
+new connections. Note that the listening socket stays active and is not
+closed.
+
+This means that new incoming connections will stay pending in the
+operating system backlog until its configurable backlog is filled.
+Once the backlog is filled, the operating system may reject further
+incoming connections until the backlog is drained again by resuming
+to accept new connections.
+
+Once the server is paused, no futher `connection` events SHOULD
+be emitted.
+
+```php
+$server->pause();
+
+$server->on('connection', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+server MAY continue emitting `connection` events.
+
+Unless otherwise noted, a successfully opened server SHOULD NOT start
+in paused state.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+Similarly, calling this after `close()` is a NO-OP.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume accepting new incoming connections.
+
+Re-attach the socket resource to the EventLoop after a previous `pause()`.
+
+```php
+$server->pause();
+
+$loop->addTimer(1.0, function () use ($server) {
+ $server->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+Similarly, calling this after `close()` is a NO-OP.
+
+#### close()
+
+The `close(): void` method can be used to
+shut down this listening socket.
+
+This will stop listening for new incoming connections on this socket.
+
+```php
+echo 'Shutting down server socket' . PHP_EOL;
+$server->close();
+```
+
+Calling this method more than once on the same instance is a NO-OP.
+
+### Server
+
+The `Server` class is the main class in this package that implements the
+[`ServerInterface`](#serverinterface) and allows you to accept incoming
+streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
+Connections can also be accepted on Unix domain sockets.
+
+```php
+$server = new Server(8080, $loop);
+```
+
+As above, the `$uri` parameter can consist of only a port, in which case the
+server will default to listening on the localhost address `127.0.0.1`,
+which means it will not be reachable from outside of this system.
+
+In order to use a random port assignment, you can use the port `0`:
+
+```php
+$server = new Server(0, $loop);
+$address = $server->getAddress();
+```
+
+In order to change the host the socket is listening on, you can provide an IP
+address through the first parameter provided to the constructor, optionally
+preceded by the `tcp://` scheme:
+
+```php
+$server = new Server('192.168.0.1:8080', $loop);
+```
+
+If you want to listen on an IPv6 address, you MUST enclose the host in square
+brackets:
+
+```php
+$server = new Server('[::1]:8080', $loop);
+```
+
+To listen on a Unix domain socket (UDS) path, you MUST prefix the URI with the
+`unix://` scheme:
+
+```php
+$server = new Server('unix:///tmp/server.sock', $loop);
+```
+
+If the given URI is invalid, does not contain a port, any other scheme or if it
+contains a hostname, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException due to missing port
+$server = new Server('127.0.0.1', $loop);
+```
+
+If the given URI appears to be valid, but listening on it fails (such as if port
+is already in use or port below 1024 may require root access etc.), it will
+throw a `RuntimeException`:
+
+```php
+$first = new Server(8080, $loop);
+
+// throws RuntimeException because port is already in use
+$second = new Server(8080, $loop);
+```
+
+> Note that these error conditions may vary depending on your system and/or
+ configuration.
+ See the exception message and code for more details about the actual error
+ condition.
+
+Optionally, you can specify [TCP socket context options](http://php.net/manual/en/context.socket.php)
+for the underlying stream socket resource like this:
+
+```php
+$server = new Server('[::1]:8080', $loop, array(
+ 'tcp' => array(
+ 'backlog' => 200,
+ 'so_reuseport' => true,
+ 'ipv6_v6only' => true
+ )
+));
+```
+
+> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+ their defaults and effects of changing these may vary depending on your system
+ and/or PHP version.
+ Passing unknown context options has no effect.
+ For BC reasons, you can also pass the TCP socket context options as a simple
+ array without wrapping this in another array under the `tcp` key.
+
+You can start a secure TLS (formerly known as SSL) server by simply prepending
+the `tls://` URI scheme.
+Internally, it will wait for plaintext TCP/IP connections and then performs a
+TLS handshake for each connection.
+It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
+which in its most basic form may look something like this if you're using a
+PEM encoded certificate file:
+
+```php
+$server = new Server('tls://127.0.0.1:8080', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem'
+ )
+));
+```
+
+> Note that the certificate file will not be loaded on instantiation but when an
+ incoming connection initializes its TLS context.
+ This implies that any invalid certificate file paths or contents will only cause
+ an `error` event at a later time.
+
+If your private key is encrypted with a passphrase, you have to specify it
+like this:
+
+```php
+$server = new Server('tls://127.0.0.1:8000', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem',
+ 'passphrase' => 'secret'
+ )
+));
+```
+
+By default, this server supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$server = new Server('tls://127.0.0.1:8000', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+ )
+));
+```
+
+> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
+ their defaults and effects of changing these may vary depending on your system
+ and/or PHP version.
+ The outer context array allows you to also use `tcp` (and possibly more)
+ context options at the same time.
+ Passing unknown context options has no effect.
+ If you do not use the `tls://` scheme, then passing `tls` context options
+ has no effect.
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+> Note that the `Server` class is a concrete implementation for TCP/IP sockets.
+ If you want to typehint in your higher-level protocol implementation, you SHOULD
+ use the generic [`ServerInterface`](#serverinterface) instead.
+
+### Advanced server usage
+
+#### TcpServer
+
+The `TcpServer` class implements the [`ServerInterface`](#serverinterface) and
+is responsible for accepting plaintext TCP/IP connections.
+
+```php
+$server = new TcpServer(8080, $loop);
+```
+
+As above, the `$uri` parameter can consist of only a port, in which case the
+server will default to listening on the localhost address `127.0.0.1`,
+which means it will not be reachable from outside of this system.
+
+In order to use a random port assignment, you can use the port `0`:
+
+```php
+$server = new TcpServer(0, $loop);
+$address = $server->getAddress();
+```
+
+In order to change the host the socket is listening on, you can provide an IP
+address through the first parameter provided to the constructor, optionally
+preceded by the `tcp://` scheme:
+
+```php
+$server = new TcpServer('192.168.0.1:8080', $loop);
+```
+
+If you want to listen on an IPv6 address, you MUST enclose the host in square
+brackets:
+
+```php
+$server = new TcpServer('[::1]:8080', $loop);
+```
+
+If the given URI is invalid, does not contain a port, any other scheme or if it
+contains a hostname, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException due to missing port
+$server = new TcpServer('127.0.0.1', $loop);
+```
+
+If the given URI appears to be valid, but listening on it fails (such as if port
+is already in use or port below 1024 may require root access etc.), it will
+throw a `RuntimeException`:
+
+```php
+$first = new TcpServer(8080, $loop);
+
+// throws RuntimeException because port is already in use
+$second = new TcpServer(8080, $loop);
+```
+
+> Note that these error conditions may vary depending on your system and/or
+configuration.
+See the exception message and code for more details about the actual error
+condition.
+
+Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php)
+for the underlying stream socket resource like this:
+
+```php
+$server = new TcpServer('[::1]:8080', $loop, array(
+ 'backlog' => 200,
+ 'so_reuseport' => true,
+ 'ipv6_v6only' => true
+));
+```
+
+> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+their defaults and effects of changing these may vary depending on your system
+and/or PHP version.
+Passing unknown context options has no effect.
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+#### SecureServer
+
+The `SecureServer` class implements the [`ServerInterface`](#serverinterface)
+and is responsible for providing a secure TLS (formerly known as SSL) server.
+
+It does so by wrapping a [`TcpServer`](#tcpserver) instance which waits for plaintext
+TCP/IP connections and then performs a TLS handshake for each connection.
+It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
+which in its most basic form may look something like this if you're using a
+PEM encoded certificate file:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem'
+));
+```
+
+> Note that the certificate file will not be loaded on instantiation but when an
+incoming connection initializes its TLS context.
+This implies that any invalid certificate file paths or contents will only cause
+an `error` event at a later time.
+
+If your private key is encrypted with a passphrase, you have to specify it
+like this:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem',
+ 'passphrase' => 'secret'
+));
+```
+
+By default, this server supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+));
+```
+
+> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
+their defaults and effects of changing these may vary depending on your system
+and/or PHP version.
+Passing unknown context options has no effect.
+
+Whenever a client completes the TLS handshake, it will emit a `connection` event
+with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+Whenever a client fails to perform a successful TLS handshake, it will emit an
+`error` event and then close the underlying TCP/IP connection:
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error' . $e->getMessage() . PHP_EOL;
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+If you want to typehint in your higher-level protocol implementation, you SHOULD
+use the generic [`ServerInterface`](#serverinterface) instead.
+
+> Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+you SHOULD pass a `TcpServer` instance as first parameter, unless you
+know what you're doing.
+Internally, the `SecureServer` has to set the required TLS context options on
+the underlying stream resources.
+These resources are not exposed through any of the interfaces defined in this
+package, but only through the internal `Connection` class.
+The `TcpServer` class is guaranteed to emit connections that implement
+the `ConnectionInterface` and uses the internal `Connection` class in order to
+expose these underlying resources.
+If you use a custom `ServerInterface` and its `connection` event does not
+meet this requirement, the `SecureServer` will emit an `error` event and
+then close the underlying connection.
+
+#### UnixServer
+
+The `UnixServer` class implements the [`ServerInterface`](#serverinterface) and
+is responsible for accepting connections on Unix domain sockets (UDS).
+
+```php
+$server = new UnixServer('/tmp/server.sock', $loop);
+```
+
+As above, the `$uri` parameter can consist of only a socket path or socket path
+prefixed by the `unix://` scheme.
+
+If the given URI appears to be valid, but listening on it fails (such as if the
+socket is already in use or the file not accessible etc.), it will throw a
+`RuntimeException`:
+
+```php
+$first = new UnixServer('/tmp/same.sock', $loop);
+
+// throws RuntimeException because socket is already in use
+$second = new UnixServer('/tmp/same.sock', $loop);
+```
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'New connection' . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+#### LimitingServer
+
+The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
+for limiting and keeping track of open connections to this server instance.
+
+Whenever the underlying server emits a `connection` event, it will check its
+limits and then either
+ - keep track of this connection by adding it to the list of
+ open connections and then forward the `connection` event
+ - or reject (close) the connection when its limits are exceeded and will
+ forward an `error` event instead.
+
+Whenever a connection closes, it will remove this connection from the list of
+open connections.
+
+```php
+$server = new LimitingServer($server, 100);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [second example](examples) for more details.
+
+You have to pass a maximum number of open connections to ensure
+the server will automatically reject (close) connections once this limit
+is exceeded. In this case, it will emit an `error` event to inform about
+this and no `connection` event will be emitted.
+
+```php
+$server = new LimitingServer($server, 100);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+You MAY pass a `null` limit in order to put no limit on the number of
+open connections and keep accepting new connection until you run out of
+operating system resources (such as open file handles). This may be
+useful if you do not want to take care of applying a limit but still want
+to use the `getConnections()` method.
+
+You can optionally configure the server to pause accepting new
+connections once the connection limit is reached. In this case, it will
+pause the underlying server and no longer process any new connections at
+all, thus also no longer closing any excessive connections.
+The underlying operating system is responsible for keeping a backlog of
+pending connections until its limit is reached, at which point it will
+start rejecting further connections.
+Once the server is below the connection limit, it will continue consuming
+connections from the backlog and will process any outstanding data on
+each connection.
+This mode may be useful for some protocols that are designed to wait for
+a response message (such as HTTP), but may be less useful for other
+protocols that demand immediate responses (such as a "welcome" message in
+an interactive chat).
+
+```php
+$server = new LimitingServer($server, 100, true);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+##### getConnections()
+
+The `getConnections(): ConnectionInterface[]` method can be used to
+return an array with all currently active connections.
+
+```php
+foreach ($server->getConnection() as $connection) {
+ $connection->write('Hi!');
+}
+```
+
+## Client usage
+
+### ConnectorInterface
+
+The `ConnectorInterface` is responsible for providing an interface for
+establishing streaming connections, such as a normal TCP/IP connection.
+
+This is the main interface defined in this package and it is used throughout
+React's vast ecosystem.
+
+Most higher-level components (such as HTTP, database or other networking
+service clients) accept an instance implementing this interface to create their
+TCP/IP connection to the underlying networking service.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against any other implementation of this interface.
+
+The interface only offers a single method:
+
+#### connect()
+
+The `connect(string $uri): PromiseInterface` method
+can be used to create a streaming connection to the given remote address.
+
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a stream implementing [`ConnectionInterface`](#connectioninterface)
+on success or rejects with an `Exception` if the connection is not successful:
+
+```php
+$connector->connect('google.com:443')->then(
+ function (ConnectionInterface $connection) {
+ // connection successfully established
+ },
+ function (Exception $error) {
+ // failed to connect due to $error
+ }
+);
+```
+
+See also [`ConnectionInterface`](#connectioninterface) for more details.
+
+The returned Promise MUST be implemented in such a way that it can be
+cancelled when it is still pending. Cancelling a pending promise MUST
+reject its value with an `Exception`. It SHOULD clean up any underlying
+resources and references as applicable:
+
+```php
+$promise = $connector->connect($uri);
+
+$promise->cancel();
+```
+
+### Connector
+
+The `Connector` class is the main class in this package that implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create streaming connections.
+
+You can use this connector to create any kind of streaming connections, such
+as plaintext TCP/IP, secure TLS or local Unix connection streams.
+
+It binds to the main event loop and can be used like this:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$connector = new Connector($loop);
+
+$connector->connect($uri)->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+In order to create a plaintext TCP/IP connection, you can simply pass a host
+and port combination like this:
+
+```php
+$connector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> If you do no specify a URI scheme in the destination URI, it will assume
+ `tcp://` as a default and establish a plaintext TCP/IP connection.
+ Note that TCP/IP connections require a host and port part in the destination
+ URI like above, all other URI components are optional.
+
+In order to create a secure TLS connection, you can use the `tls://` URI scheme
+like this:
+
+```php
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+In order to create a local Unix domain socket connection, you can use the
+`unix://` URI scheme like this:
+
+```php
+$connector->connect('unix:///tmp/demo.sock')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> The [`getRemoteAddress()`](#getremoteaddress) method will return the target
+ Unix domain socket (UDS) path as given to the `connect()` method, including
+ the `unix://` scheme, for example `unix:///tmp/demo.sock`.
+ The [`getLocalAddress()`](#getlocaladdress) method will most likely return a
+ `null` value as this value is not applicable to UDS connections here.
+
+Under the hood, the `Connector` is implemented as a *higher-level facade*
+for the lower-level connectors implemented in this package. This means it
+also shares all of their features and implementation details.
+If you want to typehint in your higher-level protocol implementation, you SHOULD
+use the generic [`ConnectorInterface`](#connectorinterface) instead.
+
+The `Connector` class will try to detect your system DNS settings (and uses
+Google's public DNS server `8.8.8.8` as a fallback if unable to determine your
+system settings) to resolve all public hostnames into underlying IP addresses by
+default.
+If you explicitly want to use a custom DNS server (such as a local DNS relay or
+a company wide DNS server), you can set up the `Connector` like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'dns' => '127.0.1.1'
+));
+
+$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+If you do not want to use a DNS resolver at all and want to connect to IP
+addresses only, you can also set up your `Connector` like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'dns' => false
+));
+
+$connector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+Advanced: If you need a custom DNS `Resolver` instance, you can also set up
+your `Connector` like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
+
+$connector = new Connector($loop, array(
+ 'dns' => $resolver
+));
+
+$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+By default, the `tcp://` and `tls://` URI schemes will use timeout value that
+repects your `default_socket_timeout` ini setting (which defaults to 60s).
+If you want a custom timeout value, you can simply pass this like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'timeout' => 10.0
+));
+```
+
+Similarly, if you do not want to apply a timeout at all and let the operating
+system handle this, you can pass a boolean flag like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'timeout' => false
+));
+```
+
+By default, the `Connector` supports the `tcp://`, `tls://` and `unix://`
+URI schemes. If you want to explicitly prohibit any of these, you can simply
+pass boolean flags like this:
+
+```php
+// only allow secure TLS connections
+$connector = new Connector($loop, array(
+ 'tcp' => false,
+ 'tls' => true,
+ 'unix' => false,
+));
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+The `tcp://` and `tls://` also accept additional context options passed to
+the underlying connectors.
+If you want to explicitly pass additional context options, you can simply
+pass arrays of context options like this:
+
+```php
+// allow insecure TLS connections
+$connector = new Connector($loop, array(
+ 'tcp' => array(
+ 'bindto' => '192.168.0.1:0'
+ ),
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ),
+));
+
+$connector->connect('tls://localhost:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+By default, this connector supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$connector = new Connector($loop, array(
+ 'tls' => array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+ )
+));
+```
+
+> For more details about context options, please refer to the PHP documentation
+ about [socket context options](http://php.net/manual/en/context.socket.php)
+ and [SSL context options](http://php.net/manual/en/context.ssl.php).
+
+Advanced: By default, the `Connector` supports the `tcp://`, `tls://` and
+`unix://` URI schemes.
+For this, it sets up the required connector classes automatically.
+If you want to explicitly pass custom connectors for any of these, you can simply
+pass an instance implementing the `ConnectorInterface` like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
+$tcp = new DnsConnector(new TcpConnector($loop), $resolver);
+
+$tls = new SecureConnector($tcp, $loop);
+
+$unix = new UnixConnector($loop);
+
+$connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'tls' => $tls,
+ 'unix' => $unix,
+
+ 'dns' => false,
+ 'timeout' => false,
+));
+
+$connector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> Internally, the `tcp://` connector will always be wrapped by the DNS resolver,
+ unless you disable DNS like in the above example. In this case, the `tcp://`
+ connector receives the actual hostname instead of only the resolved IP address
+ and is thus responsible for performing the lookup.
+ Internally, the automatically created `tls://` connector will always wrap the
+ underlying `tcp://` connector for establishing the underlying plaintext
+ TCP/IP connection before enabling secure TLS mode. If you want to use a custom
+ underlying `tcp://` connector for secure TLS connections only, you may
+ explicitly pass a `tls://` connector like above instead.
+ Internally, the `tcp://` and `tls://` connectors will always be wrapped by
+ `TimeoutConnector`, unless you disable timeouts like in the above example.
+
+### Advanced client usage
+
+#### TcpConnector
+
+The `React\Socket\TcpConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext
+TCP/IP connections to any IP-port-combination:
+
+```php
+$tcpConnector = new React\Socket\TcpConnector($loop);
+
+$tcpConnector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $tcpConnector->connect('127.0.0.1:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will close the underlying socket
+resource, thus cancelling the pending TCP/IP connection, and reject the
+resulting promise.
+
+You can optionally pass additional
+[socket context options](http://php.net/manual/en/context.socket.php)
+to the constructor like this:
+
+```php
+$tcpConnector = new React\Socket\TcpConnector($loop, array(
+ 'bindto' => '192.168.0.1:0'
+));
+```
+
+Note that this class only allows you to connect to IP-port-combinations.
+If the given URI is invalid, does not contain a valid IP address and port
+or contains any other scheme, it will reject with an
+`InvalidArgumentException`:
+
+If the given URI appears to be valid, but connecting to it fails (such as if
+the remote host rejects the connection etc.), it will reject with a
+`RuntimeException`.
+
+If you want to connect to hostname-port-combinations, see also the following chapter.
+
+> Advanced usage: Internally, the `TcpConnector` allocates an empty *context*
+resource for each stream resource.
+If the destination URI contains a `hostname` query parameter, its value will
+be used to set up the TLS peer name.
+This is used by the `SecureConnector` and `DnsConnector` to verify the peer
+name and can also be used if you want a custom TLS peer name.
+
+#### DnsConnector
+
+The `DnsConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext
+TCP/IP connections to any hostname-port-combination.
+
+It does so by decorating a given `TcpConnector` instance so that it first
+looks up the given domain name via DNS (if applicable) and then establishes the
+underlying TCP/IP connection to the resolved target IP address.
+
+Make sure to set up your DNS resolver and underlying TCP connector like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop);
+
+$dnsConnector = new React\Socket\DnsConnector($tcpConnector, $dns);
+
+$dnsConnector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $dnsConnector->connect('www.google.com:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying DNS lookup
+and/or the underlying TCP/IP connection and reject the resulting promise.
+
+> Advanced usage: Internally, the `DnsConnector` relies on a `Resolver` to
+look up the IP address for the given hostname.
+It will then replace the hostname in the destination URI with this IP and
+append a `hostname` query parameter and pass this updated URI to the underlying
+connector.
+The underlying connector is thus responsible for creating a connection to the
+target IP address, while this query parameter can be used to check the original
+hostname and is used by the `TcpConnector` to set up the TLS peer name.
+If a `hostname` is given explicitly, this query parameter will not be modified,
+which can be useful if you want a custom TLS peer name.
+
+#### SecureConnector
+
+The `SecureConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create secure
+TLS (formerly known as SSL) connections to any hostname-port-combination.
+
+It does so by decorating a given `DnsConnector` instance so that it first
+creates a plaintext TCP/IP connection and then enables TLS encryption on this
+stream.
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop);
+
+$secureConnector->connect('www.google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
+ ...
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $secureConnector->connect('www.google.com:443');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
+connection and/or the SSL/TLS negotiation and reject the resulting promise.
+
+You can optionally pass additional
+[SSL context options](http://php.net/manual/en/context.ssl.php)
+to the constructor like this:
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+));
+```
+
+By default, this connector supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+));
+```
+
+> Advanced usage: Internally, the `SecureConnector` relies on setting up the
+required *context options* on the underlying stream resource.
+It should therefor be used with a `TcpConnector` somewhere in the connector
+stack so that it can allocate an empty *context* resource for each stream
+resource and verify the peer name.
+Failing to do so may result in a TLS peer name mismatch error or some hard to
+trace race conditions, because all stream resources will use a single, shared
+*default context* resource otherwise.
+
+#### TimeoutConnector
+
+The `TimeoutConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to add timeout
+handling to any existing connector instance.
+
+It does so by decorating any given [`ConnectorInterface`](#connectorinterface)
+instance and starting a timer that will automatically reject and abort any
+underlying connection attempt if it takes too long.
+
+```php
+$timeoutConnector = new React\Socket\TimeoutConnector($connector, 3.0, $loop);
+
+$timeoutConnector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $timeoutConnector->connect('google.com:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying connection
+attempt, abort the timer and reject the resulting promise.
+
+#### UnixConnector
+
+The `UnixConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to connect to
+Unix domain socket (UDS) paths like this:
+
+```php
+$connector = new React\Socket\UnixConnector($loop);
+
+$connector->connect('/tmp/demo.sock')->then(function (ConnectionInterface $connection) {
+ $connection->write("HELLO\n");
+});
+
+$loop->run();
+```
+
+Connecting to Unix domain sockets is an atomic operation, i.e. its promise will
+settle (either resolve or reject) immediately.
+As such, calling `cancel()` on the resulting promise has no effect.
+
+> The [`getRemoteAddress()`](#getremoteaddress) method will return the target
+ Unix domain socket (UDS) path as given to the `connect()` method, prepended
+ with the `unix://` scheme, for example `unix:///tmp/demo.sock`.
+ The [`getLocalAddress()`](#getlocaladdress) method will most likely return a
+ `null` value as this value is not applicable to UDS connections here.
+
+#### FixedUriConnector
+
+The `FixedUriConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and decorates an existing Connector
+to always use a fixed, preconfigured URI.
+
+This can be useful for consumers that do not support certain URIs, such as
+when you want to explicitly connect to a Unix domain socket (UDS) path
+instead of connecting to a default address assumed by an higher-level API:
+
+```php
+$connector = new FixedUriConnector(
+ 'unix:///var/run/docker.sock',
+ new UnixConnector($loop)
+);
+
+// destination will be ignored, actually connects to Unix domain socket
+$promise = $connector->connect('localhost:80');
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/socket:^0.8.10
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project, partly due to its vast
+performance improvements and partly because legacy PHP versions require several
+workarounds as described below.
+
+Secure TLS connections received some major upgrades starting with PHP 5.6, with
+the defaults now being more secure, while older versions required explicit
+context options.
+This library does not take responsibility over these context options, so it's
+up to consumers of this library to take care of setting appropriate context
+options as described above.
+
+All versions of PHP prior to 5.6.8 suffered from a buffering issue where reading
+from a streaming TLS connection could be one `data` event behind.
+This library implements a work-around to try to flush the complete incoming
+data buffers on these legacy PHP versions, which has a penalty of around 10% of
+throughput on all connections.
+With this work-around, we have not been able to reproduce this issue anymore,
+but we have seen reports of people saying this could still affect some of the
+older PHP versions (`5.5.23`, `5.6.7`, and `5.6.8`).
+Note that this only affects *some* higher-level streaming protocols, such as
+IRC over TLS, but should not affect HTTP over TLS (HTTPS).
+Further investigation of this issue is needed.
+For more insights, this issue is also covered by our test suite.
+
+PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
+chunks of data over TLS streams at once.
+We try to work around this by limiting the write chunk size to 8192
+bytes for older PHP versions only.
+This is only a work-around and has a noticable performance penalty on
+affected versions.
+
+This project also supports running on HHVM.
+Note that really old HHVM < 3.8 does not support secure TLS connections, as it
+lacks the required `stream_socket_enable_crypto()` function.
+As such, trying to create a secure TLS connections on affected versions will
+return a rejected promise instead.
+This issue is also covered by our test suite, which will skip related tests
+on affected versions.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/assets/php/vendor/react/socket/composer.json b/assets/php/vendor/react/socket/composer.json
new file mode 100644
index 0000000..bc85aab
--- /dev/null
+++ b/assets/php/vendor/react/socket/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "react/socket",
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/dns": "^0.4.13",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/stream": "^1.0 || ^0.7.1",
+ "react/promise": "^2.1 || ^1.2",
+ "react/promise-timer": "~1.0"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Socket\\": "tests"
+ }
+ }
+}
diff --git a/assets/php/vendor/react/socket/examples/01-echo-server.php b/assets/php/vendor/react/socket/examples/01-echo-server.php
new file mode 100644
index 0000000..2c0be57
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/01-echo-server.php
@@ -0,0 +1,42 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) {
+ echo '[' . $conn->getRemoteAddress() . ' connected]' . PHP_EOL;
+ $conn->pipe($conn);
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/assets/php/vendor/react/socket/examples/02-chat-server.php b/assets/php/vendor/react/socket/examples/02-chat-server.php
new file mode 100644
index 0000000..46439e0
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/02-chat-server.php
@@ -0,0 +1,59 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server = new LimitingServer($server, null);
+
+$server->on('connection', function (ConnectionInterface $client) use ($server) {
+ // whenever a new message comes in
+ $client->on('data', function ($data) use ($client, $server) {
+ // remove any non-word characters (just for the demo)
+ $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data));
+
+ // ignore empty messages
+ if ($data === '') {
+ return;
+ }
+
+ // prefix with client IP and broadcast to all connected clients
+ $data = trim(parse_url($client->getRemoteAddress(), PHP_URL_HOST), '[]') . ': ' . $data . PHP_EOL;
+ foreach ($server->getConnections() as $connection) {
+ $connection->write($data);
+ }
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/assets/php/vendor/react/socket/examples/03-http-server.php b/assets/php/vendor/react/socket/examples/03-http-server.php
new file mode 100644
index 0000000..eb6d454
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/03-http-server.php
@@ -0,0 +1,57 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) {
+ $conn->once('data', function () use ($conn) {
+ $body = "
Hello world!
\r\n";
+ $conn->end("HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\nConnection: close\r\n\r\n" . $body);
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . strtr($server->getAddress(), array('tcp:' => 'http:', 'tls:' => 'https:')) . PHP_EOL;
+
+$loop->run();
diff --git a/assets/php/vendor/react/socket/examples/11-http-client.php b/assets/php/vendor/react/socket/examples/11-http-client.php
new file mode 100644
index 0000000..2b64a43
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/11-http-client.php
@@ -0,0 +1,36 @@
+connect($host. ':80')->then(function (ConnectionInterface $connection) use ($host) {
+ $connection->on('data', function ($data) {
+ echo $data;
+ });
+ $connection->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+ });
+
+ $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/assets/php/vendor/react/socket/examples/12-https-client.php b/assets/php/vendor/react/socket/examples/12-https-client.php
new file mode 100644
index 0000000..6e3f279
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/12-https-client.php
@@ -0,0 +1,36 @@
+connect('tls://' . $host . ':443')->then(function (ConnectionInterface $connection) use ($host) {
+ $connection->on('data', function ($data) {
+ echo $data;
+ });
+ $connection->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+ });
+
+ $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/assets/php/vendor/react/socket/examples/21-netcat-client.php b/assets/php/vendor/react/socket/examples/21-netcat-client.php
new file mode 100644
index 0000000..9140e2c
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/21-netcat-client.php
@@ -0,0 +1,68 @@
+' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+$connector = new Connector($loop);
+
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdin->pause();
+$stdout = new WritableResourceStream(STDOUT, $loop);
+$stderr = new WritableResourceStream(STDERR, $loop);
+
+$stderr->write('Connecting' . PHP_EOL);
+
+$connector->connect($argv[1])->then(function (ConnectionInterface $connection) use ($stdin, $stdout, $stderr) {
+ // pipe everything from STDIN into connection
+ $stdin->resume();
+ $stdin->pipe($connection);
+
+ // pipe everything from connection to STDOUT
+ $connection->pipe($stdout);
+
+ // report errors to STDERR
+ $connection->on('error', function ($error) use ($stderr) {
+ $stderr->write('Stream ERROR: ' . $error . PHP_EOL);
+ });
+
+ // report closing and stop reading from input
+ $connection->on('close', function () use ($stderr, $stdin) {
+ $stderr->write('[CLOSED]' . PHP_EOL);
+ $stdin->close();
+ });
+
+ $stderr->write('Connected' . PHP_EOL);
+}, function ($error) use ($stderr) {
+ $stderr->write('Connection ERROR: ' . $error . PHP_EOL);
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/socket/examples/22-http-client.php b/assets/php/vendor/react/socket/examples/22-http-client.php
new file mode 100644
index 0000000..fcb8107
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/22-http-client.php
@@ -0,0 +1,60 @@
+' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+$connector = new Connector($loop);
+
+if (!isset($parts['port'])) {
+ $parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
+}
+
+$host = $parts['host'];
+if (($parts['scheme'] === 'http' && $parts['port'] !== 80) || ($parts['scheme'] === 'https' && $parts['port'] !== 443)) {
+ $host .= ':' . $parts['port'];
+}
+$target = ($parts['scheme'] === 'https' ? 'tls' : 'tcp') . '://' . $parts['host'] . ':' . $parts['port'];
+$resource = isset($parts['path']) ? $parts['path'] : '/';
+if (isset($parts['query'])) {
+ $resource .= '?' . $parts['query'];
+}
+
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$connector->connect($target)->then(function (ConnectionInterface $connection) use ($resource, $host, $stdout) {
+ $connection->pipe($stdout);
+
+ $connection->write("GET $resource HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/assets/php/vendor/react/socket/examples/91-benchmark-server.php b/assets/php/vendor/react/socket/examples/91-benchmark-server.php
new file mode 100644
index 0000000..420d474
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/91-benchmark-server.php
@@ -0,0 +1,60 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
+ echo '[connected]' . PHP_EOL;
+
+ // count the number of bytes received from this connection
+ $bytes = 0;
+ $conn->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ // report average throughput once client disconnects
+ $t = microtime(true);
+ $conn->on('close', function () use ($conn, $t, &$bytes) {
+ $t = microtime(true) - $t;
+ echo '[disconnected after receiving ' . $bytes . ' bytes in ' . round($t, 3) . 's => ' . round($bytes / $t / 1024 / 1024, 1) . ' MiB/s]' . PHP_EOL;
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/assets/php/vendor/react/socket/examples/99-generate-self-signed.php b/assets/php/vendor/react/socket/examples/99-generate-self-signed.php
new file mode 100644
index 0000000..00f9314
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/99-generate-self-signed.php
@@ -0,0 +1,31 @@
+ secret.pem
+
+// certificate details (Distinguished Name)
+// (OpenSSL applies defaults to missing fields)
+$dn = array(
+ "commonName" => isset($argv[1]) ? $argv[1] : "localhost",
+// "countryName" => "AU",
+// "stateOrProvinceName" => "Some-State",
+// "localityName" => "London",
+// "organizationName" => "Internet Widgits Pty Ltd",
+// "organizationalUnitName" => "R&D",
+// "emailAddress" => "admin@example.com"
+);
+
+// create certificate which is valid for ~10 years
+$privkey = openssl_pkey_new();
+$cert = openssl_csr_new($dn, $privkey);
+$cert = openssl_csr_sign($cert, null, $privkey, 3650);
+
+// export public and (optionally encrypted) private key in PEM format
+openssl_x509_export($cert, $out);
+echo $out;
+
+$passphrase = isset($argv[2]) ? $argv[2] : null;
+openssl_pkey_export($privkey, $out, $passphrase);
+echo $out;
diff --git a/assets/php/vendor/react/socket/examples/localhost.pem b/assets/php/vendor/react/socket/examples/localhost.pem
new file mode 100644
index 0000000..be69279
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/localhost.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
+MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
+BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
+0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
+7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
+824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
+V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
+IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
+ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
+g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
+tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
+LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
+tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
+9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
+43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
+pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
+OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
+2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
+li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
+b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
+vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
+XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
+Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
+iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
++EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
+y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
+81oh1uCH1YPLM29hPyaohxL8
+-----END PRIVATE KEY-----
diff --git a/assets/php/vendor/react/socket/examples/localhost_swordfish.pem b/assets/php/vendor/react/socket/examples/localhost_swordfish.pem
new file mode 100644
index 0000000..7d1ee80
--- /dev/null
+++ b/assets/php/vendor/react/socket/examples/localhost_swordfish.pem
@@ -0,0 +1,51 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQxMDQzWhcNMjYx
+MjI4MTQxMDQzWjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRXt83SrKIHr/i
+3lc8O8pz6NHE1DNHJa4xg2xalXWzCEV6m1qLd9VdaLT9cJD1afNmEMBgY6RblNL/
+paJWVoR9MOUeIoYl2PrhUCxsf7h6MRtezQQe3e+n+/0XunF0JUQIZuJqbxfRk5WT
+XmYnphqOZKEcistAYvFBjzl/D+Cl/nYsreADc+t9l5Vni89oTWEuqIrsM4WUZqqB
+VMAakd2nZJLWIrMxq9hbW1XNukOQfcmZVFTC6CUnLq8qzGbtfZYBuMBACnL1k/E/
+yPaAgR46l14VAcndDUJBtMeL2qYuNwvXQhg3KuBmpTUpH+yzxU+4T3lmv0xXmPqu
+ySH3xvW3AgMBAAGjUDBOMB0GA1UdDgQWBBRu68WTI4pVeTB7wuG9QGI3Ie441TAf
+BgNVHSMEGDAWgBRu68WTI4pVeTB7wuG9QGI3Ie441TAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQCc4pEjEHO47VRJkbHgC+c2gAVgxekkaA1czBA1uAvh
+ILRda0NLlvyftbjaG0zZp2ABUCfRfksl/Pf/PzWLUMEuH/9kEW2rgP43z6YgiL6k
+kBPlmAU607UjD726RPGkw8QPSXS/dWiNJ5CBpPWLpxC45pokqItYbY0ijQ5Piq09
+TchYlCX044oSRnPiP394PQ3HVdaGhJB2DnjDq3in5dVivFf8EdgzQSvp/wXy3WQs
+uFSVonSnrZGY/4AgT3psGaQ6fqKb4SBoqtf5bFQvp1XNNRkuEJnS/0dygEya0c+c
+aCe/1gXC2wDjx0/TekY5m1Nyw5SY6z7stOqL/ekwgejt
+-----END CERTIFICATE-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIG7idPRLgiHkCAggA
+MBQGCCqGSIb3DQMHBAg+MLPdepHWSwSCBMgVW9LseCjfTAmF9U1qRnKsq3kIwEnW
+6aERBqs/mnmEhrXgZYgcvRRK7kD12TdHt/Nz46Ymu0h+Lrvuwtl1fHQUARTk/gFh
+onLhc9kjMUhLRIR007vJe3HvWOb/v+SBSDB38OpUxUwJmBVBuSaYLWVuPR6J5kUj
+xOgBS049lN3E9cfrHvb3bF/epIQrU0OgfyyxEvIi5n30y+tlRn3y68PY6Qd46t4Y
+UN5VZUwvJBgoRy9TGxSkiSRjhxC2PWpLYq/HMzDbcRcFF5dVAIioUd/VZ7fdgBfA
+uMW4SFfpFLDUX0aaYe+ZdA5tM0Bc0cOtG8Z0sc9JYDNNcvjmSiGCi646h8F0D3O6
+JKAQMMxQGWiyQeJ979LVjtq4lJESXA8VEKz9rV03y5xunmFCLy6dGt+6GJwXgabn
+OH7nvEv4GqAOqKc6E9je4JM+AF/oUazrfPse1KEEtsPKarazjCB/SKYtHyDJaavD
+GGjtiU9zWwGMOgIDyNmXe3ga7/TWoGOAg5YlTr6Hbq2Y/5ycgjAgPFjuXtvnoT0+
+mF5TnNfMAqTgQsE2gjhonK1pdlOen0lN5FtoUXp3CXU0dOq0J70GiX+1YA7VDn30
+n5WNAgfOXX3l3E95jGN370pHXyli5RUNW0NZVHV+22jlNWCVtQHUh+DVswQZg+i5
++DqaIHz2jUetMo7gWtqGn/wwSopOs87VM1rcALhZL4EsJ+Zy81I/hA32RNnGbuol
+NAiZh+0KrtTcc/fPunpd8vRtOwGphM11dKucozUufuiPG2inR3aEqt5yNx54ec/f
+J6nryWRYiHEA/rCU9MSBM9cqKFtEmy9/8oxV41/SPxhXjHwDlABWTtFuJ3pf2sOF
+ILSYYFwB0ZGvdjE5yAJFBr9efno/L9fafmGk7a3vmVgK2AmUC9VNB5XHw1GjF8OP
+aQAXe4md9Bh0jk/D/iyp7e7IWNssul/7XejhabidWgFj6EXc9YxE59+FlhDqyMhn
+V6houc+QeUXuwsAKgRJJhJtpv/QSZ5BI3esxHHUt3ayGnvhFElpAc0t7C/EiXKIv
+DAFYP2jksBqijM8YtEgPWYzEP5buYxZnf/LK7FDocLsNcdF38UaKBbeF90e7bR8j
+SHspG9aJWICu8Yawnh8zuy/vQv+h9gWyGodd2p9lQzlbRXrutbwfmPf7xP6nzT9i
+9GcugJxTaZgkCfhhHxFk/nRHS2NAzagKVib1xkUlZJg2hX0fIFUdYteL1GGTvOx5
+m3mTOino4T19z9SEdZYb2OHYh29e/T74bJiLCYdXwevSYHxfZc8pYAf0jp4UnMT2
+f7B0ctX1iXuQ2uZVuxh+U1Mcu+v0gDla1jWh7AhcePSi4xBNUCak0kQip6r5e6Oi
+r4MIyMRk/Pc5pzEKo8G6nk26rNvX3aRvECoVfmK7IVdsqZ6IXlt9kOmWx3IeKzrO
+J5DxpzW+9oIRZJgPTkc4/XRb0tFmFQYTiChiQ1AJUEiCX0GpkFf7cq61aLGYtWyn
+vL2lmQhljzjrDo15hKErvk7eBZW7GW/6j/m/PfRdcBI4ceuP9zWQXnDOd9zmaE4b
+q3bJ+IbbyVZA2WwyzN7umCKWghsiPMAolxEnYM9JRf8BcqeqQiwVZlfO5KFuN6Ze
+le4=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/assets/php/vendor/react/socket/phpunit.xml.dist b/assets/php/vendor/react/socket/phpunit.xml.dist
new file mode 100644
index 0000000..13d3fab
--- /dev/null
+++ b/assets/php/vendor/react/socket/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/assets/php/vendor/react/socket/src/Connection.php b/assets/php/vendor/react/socket/src/Connection.php
new file mode 100644
index 0000000..c6267cc
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/Connection.php
@@ -0,0 +1,178 @@
+= 70100 && PHP_VERSION_ID < 70104));
+
+ $this->input = new DuplexResourceStream(
+ $resource,
+ $loop,
+ $clearCompleteBuffer ? -1 : null,
+ new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
+ );
+
+ $this->stream = $resource;
+
+ Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
+
+ $this->input->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->input->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->input->isWritable();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return $this->input->pipe($dest, $options);
+ }
+
+ public function write($data)
+ {
+ return $this->input->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->input->end($data);
+ }
+
+ public function close()
+ {
+ $this->input->close();
+ $this->handleClose();
+ $this->removeAllListeners();
+ }
+
+ public function handleClose()
+ {
+ if (!is_resource($this->stream)) {
+ return;
+ }
+
+ // Try to cleanly shut down socket and ignore any errors in case other
+ // side already closed. Shutting down may return to blocking mode on
+ // some legacy versions, so reset to non-blocking just in case before
+ // continuing to close the socket resource.
+ // Underlying Stream implementation will take care of closing file
+ // handle, so we otherwise keep this open here.
+ @stream_socket_shutdown($this->stream, STREAM_SHUT_RDWR);
+ stream_set_blocking($this->stream, false);
+ }
+
+ public function getRemoteAddress()
+ {
+ return $this->parseAddress(@stream_socket_get_name($this->stream, true));
+ }
+
+ public function getLocalAddress()
+ {
+ return $this->parseAddress(@stream_socket_get_name($this->stream, false));
+ }
+
+ private function parseAddress($address)
+ {
+ if ($address === false) {
+ return null;
+ }
+
+ if ($this->unix) {
+ // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
+ // note that technically ":" is a valid address, so keep this in place otherwise
+ if (substr($address, -1) === ':' && defined('HHVM_VERSION_ID') && HHVM_VERSION_ID < 31900) {
+ $address = (string)substr($address, 0, -1);
+ }
+
+ // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
+ // PHP uses "\0" string and HHVM uses empty string (colon removed above)
+ if ($address === '' || $address[0] === "\x00" ) {
+ return null;
+ }
+
+ return 'unix://' . $address;
+ }
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = strrpos($address, ':');
+ if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
+ $port = substr($address, $pos + 1);
+ $address = '[' . substr($address, 0, $pos) . ']:' . $port;
+ }
+
+ return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/ConnectionInterface.php b/assets/php/vendor/react/socket/src/ConnectionInterface.php
new file mode 100644
index 0000000..64613b5
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/ConnectionInterface.php
@@ -0,0 +1,119 @@
+on('data', function ($chunk) {
+ * echo $chunk;
+ * });
+ *
+ * $connection->on('end', function () {
+ * echo 'ended';
+ * });
+ *
+ * $connection->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage();
+ * });
+ *
+ * $connection->on('close', function () {
+ * echo 'closed';
+ * });
+ *
+ * $connection->write($data);
+ * $connection->end($data = null);
+ * $connection->close();
+ * // …
+ * ```
+ *
+ * For more details, see the
+ * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+ *
+ * @see DuplexStreamInterface
+ * @see ServerInterface
+ * @see ConnectorInterface
+ */
+interface ConnectionInterface extends DuplexStreamInterface
+{
+ /**
+ * Returns the full remote address (URI) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the remote address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based connection and you only want the remote IP, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * $ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+ * echo 'Connection with ' . $ip . PHP_EOL;
+ * ```
+ *
+ * @return ?string remote address (URI) or null if unknown
+ */
+ public function getRemoteAddress();
+
+ /**
+ * Returns the full local address (full URI with scheme, IP and port) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getLocalAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the local address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+ * so they should not be confused.
+ *
+ * If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+ * the address `0.0.0.0`), you can use this method to find out which interface
+ * actually accepted this connection (such as a public or local interface).
+ *
+ * If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+ * you can use this method to find out which interface was actually
+ * used for this connection.
+ *
+ * @return ?string local address (URI) or null if unknown
+ * @see self::getRemoteAddress()
+ */
+ public function getLocalAddress();
+}
diff --git a/assets/php/vendor/react/socket/src/Connector.php b/assets/php/vendor/react/socket/src/Connector.php
new file mode 100644
index 0000000..75276bc
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/Connector.php
@@ -0,0 +1,136 @@
+ true,
+ 'tls' => true,
+ 'unix' => true,
+
+ 'dns' => true,
+ 'timeout' => true,
+ );
+
+ if ($options['timeout'] === true) {
+ $options['timeout'] = (float)ini_get("default_socket_timeout");
+ }
+
+ if ($options['tcp'] instanceof ConnectorInterface) {
+ $tcp = $options['tcp'];
+ } else {
+ $tcp = new TcpConnector(
+ $loop,
+ is_array($options['tcp']) ? $options['tcp'] : array()
+ );
+ }
+
+ if ($options['dns'] !== false) {
+ if ($options['dns'] instanceof Resolver) {
+ $resolver = $options['dns'];
+ } else {
+ if ($options['dns'] !== true) {
+ $server = $options['dns'];
+ } else {
+ // try to load nameservers from system config or default to Google's public DNS
+ $config = Config::loadSystemConfigBlocking();
+ $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+ }
+
+ $factory = new Factory();
+ $resolver = $factory->create(
+ $server,
+ $loop
+ );
+ }
+
+ $tcp = new DnsConnector($tcp, $resolver);
+ }
+
+ if ($options['tcp'] !== false) {
+ $options['tcp'] = $tcp;
+
+ if ($options['timeout'] !== false) {
+ $options['tcp'] = new TimeoutConnector(
+ $options['tcp'],
+ $options['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tcp'] = $options['tcp'];
+ }
+
+ if ($options['tls'] !== false) {
+ if (!$options['tls'] instanceof ConnectorInterface) {
+ $options['tls'] = new SecureConnector(
+ $tcp,
+ $loop,
+ is_array($options['tls']) ? $options['tls'] : array()
+ );
+ }
+
+ if ($options['timeout'] !== false) {
+ $options['tls'] = new TimeoutConnector(
+ $options['tls'],
+ $options['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tls'] = $options['tls'];
+ }
+
+ if ($options['unix'] !== false) {
+ if (!$options['unix'] instanceof ConnectorInterface) {
+ $options['unix'] = new UnixConnector($loop);
+ }
+ $this->connectors['unix'] = $options['unix'];
+ }
+ }
+
+ public function connect($uri)
+ {
+ $scheme = 'tcp';
+ if (strpos($uri, '://') !== false) {
+ $scheme = (string)substr($uri, 0, strpos($uri, '://'));
+ }
+
+ if (!isset($this->connectors[$scheme])) {
+ return Promise\reject(new RuntimeException(
+ 'No connector available for URI scheme "' . $scheme . '"'
+ ));
+ }
+
+ return $this->connectors[$scheme]->connect($uri);
+ }
+}
+
diff --git a/assets/php/vendor/react/socket/src/ConnectorInterface.php b/assets/php/vendor/react/socket/src/ConnectorInterface.php
new file mode 100644
index 0000000..196d01a
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/ConnectorInterface.php
@@ -0,0 +1,58 @@
+connect('google.com:443')->then(
+ * function (ConnectionInterface $connection) {
+ * // connection successfully established
+ * },
+ * function (Exception $error) {
+ * // failed to connect due to $error
+ * }
+ * );
+ * ```
+ *
+ * The returned Promise MUST be implemented in such a way that it can be
+ * cancelled when it is still pending. Cancelling a pending promise MUST
+ * reject its value with an Exception. It SHOULD clean up any underlying
+ * resources and references as applicable.
+ *
+ * ```php
+ * $promise = $connector->connect($uri);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $uri
+ * @return \React\Promise\PromiseInterface resolves with a stream implementing ConnectionInterface on success or rejects with an Exception on error
+ * @see ConnectionInterface
+ */
+ public function connect($uri);
+}
diff --git a/assets/php/vendor/react/socket/src/DnsConnector.php b/assets/php/vendor/react/socket/src/DnsConnector.php
new file mode 100644
index 0000000..90170e5
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/DnsConnector.php
@@ -0,0 +1,111 @@
+connector = $connector;
+ $this->resolver = $resolver;
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $parts = parse_url('tcp://' . $uri);
+ unset($parts['scheme']);
+ } else {
+ $parts = parse_url($uri);
+ }
+
+ if (!$parts || !isset($parts['host'])) {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $connector = $this->connector;
+
+ return $this
+ ->resolveHostname($host)
+ ->then(function ($ip) use ($connector, $host, $parts) {
+ $uri = '';
+
+ // prepend original scheme if known
+ if (isset($parts['scheme'])) {
+ $uri .= $parts['scheme'] . '://';
+ }
+
+ if (strpos($ip, ':') !== false) {
+ // enclose IPv6 addresses in square brackets before appending port
+ $uri .= '[' . $ip . ']';
+ } else {
+ $uri .= $ip;
+ }
+
+ // append original port if known
+ if (isset($parts['port'])) {
+ $uri .= ':' . $parts['port'];
+ }
+
+ // append orignal path if known
+ if (isset($parts['path'])) {
+ $uri .= $parts['path'];
+ }
+
+ // append original query if known
+ if (isset($parts['query'])) {
+ $uri .= '?' . $parts['query'];
+ }
+
+ // append original hostname as query if resolved via DNS and if
+ // destination URI does not contain "hostname" query param already
+ $args = array();
+ parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
+ if ($host !== $ip && !isset($args['hostname'])) {
+ $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . rawurlencode($host);
+ }
+
+ // append original fragment if known
+ if (isset($parts['fragment'])) {
+ $uri .= '#' . $parts['fragment'];
+ }
+
+ return $connector->connect($uri);
+ });
+ }
+
+ private function resolveHostname($host)
+ {
+ if (false !== filter_var($host, FILTER_VALIDATE_IP)) {
+ return Promise\resolve($host);
+ }
+
+ $promise = $this->resolver->resolve($host);
+
+ return new Promise\Promise(
+ function ($resolve, $reject) use ($promise) {
+ // resolve/reject with result of DNS lookup
+ $promise->then($resolve, $reject);
+ },
+ function ($_, $reject) use ($promise) {
+ // cancellation should reject connection attempt
+ $reject(new RuntimeException('Connection attempt cancelled during DNS lookup'));
+
+ // (try to) cancel pending DNS lookup
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ );
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/FixedUriConnector.php b/assets/php/vendor/react/socket/src/FixedUriConnector.php
new file mode 100644
index 0000000..057bcdf
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/FixedUriConnector.php
@@ -0,0 +1,41 @@
+connect('localhost:80');
+ * ```
+ */
+class FixedUriConnector implements ConnectorInterface
+{
+ private $uri;
+ private $connector;
+
+ /**
+ * @param string $uri
+ * @param ConnectorInterface $connector
+ */
+ public function __construct($uri, ConnectorInterface $connector)
+ {
+ $this->uri = $uri;
+ $this->connector = $connector;
+ }
+
+ public function connect($_)
+ {
+ return $this->connector->connect($this->uri);
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/LimitingServer.php b/assets/php/vendor/react/socket/src/LimitingServer.php
new file mode 100644
index 0000000..c7874ee
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/LimitingServer.php
@@ -0,0 +1,203 @@
+on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+class LimitingServer extends EventEmitter implements ServerInterface
+{
+ private $connections = array();
+ private $server;
+ private $limit;
+
+ private $pauseOnLimit = false;
+ private $autoPaused = false;
+ private $manuPaused = false;
+
+ /**
+ * Instantiates a new LimitingServer.
+ *
+ * You have to pass a maximum number of open connections to ensure
+ * the server will automatically reject (close) connections once this limit
+ * is exceeded. In this case, it will emit an `error` event to inform about
+ * this and no `connection` event will be emitted.
+ *
+ * ```php
+ * $server = new LimitingServer($server, 100);
+ * $server->on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * You MAY pass a `null` limit in order to put no limit on the number of
+ * open connections and keep accepting new connection until you run out of
+ * operating system resources (such as open file handles). This may be
+ * useful if you do not want to take care of applying a limit but still want
+ * to use the `getConnections()` method.
+ *
+ * You can optionally configure the server to pause accepting new
+ * connections once the connection limit is reached. In this case, it will
+ * pause the underlying server and no longer process any new connections at
+ * all, thus also no longer closing any excessive connections.
+ * The underlying operating system is responsible for keeping a backlog of
+ * pending connections until its limit is reached, at which point it will
+ * start rejecting further connections.
+ * Once the server is below the connection limit, it will continue consuming
+ * connections from the backlog and will process any outstanding data on
+ * each connection.
+ * This mode may be useful for some protocols that are designed to wait for
+ * a response message (such as HTTP), but may be less useful for other
+ * protocols that demand immediate responses (such as a "welcome" message in
+ * an interactive chat).
+ *
+ * ```php
+ * $server = new LimitingServer($server, 100, true);
+ * $server->on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * @param ServerInterface $server
+ * @param int|null $connectionLimit
+ * @param bool $pauseOnLimit
+ */
+ public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
+ {
+ $this->server = $server;
+ $this->limit = $connectionLimit;
+ if ($connectionLimit !== null) {
+ $this->pauseOnLimit = $pauseOnLimit;
+ }
+
+ $this->server->on('connection', array($this, 'handleConnection'));
+ $this->server->on('error', array($this, 'handleError'));
+ }
+
+ /**
+ * Returns an array with all currently active connections
+ *
+ * ```php
+ * foreach ($server->getConnection() as $connection) {
+ * $connection->write('Hi!');
+ * }
+ * ```
+ *
+ * @return ConnectionInterface[]
+ */
+ public function getConnections()
+ {
+ return $this->connections;
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ if (!$this->manuPaused) {
+ $this->manuPaused = true;
+
+ if (!$this->autoPaused) {
+ $this->server->pause();
+ }
+ }
+ }
+
+ public function resume()
+ {
+ if ($this->manuPaused) {
+ $this->manuPaused = false;
+
+ if (!$this->autoPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ // close connection if limit exceeded
+ if ($this->limit !== null && count($this->connections) >= $this->limit) {
+ $this->handleError(new OverflowException('Connection closed because server reached connection limit'));
+ $connection->close();
+ return;
+ }
+
+ $this->connections[] = $connection;
+ $that = $this;
+ $connection->on('close', function () use ($that, $connection) {
+ $that->handleDisconnection($connection);
+ });
+
+ // pause accepting new connections if limit exceeded
+ if ($this->pauseOnLimit && !$this->autoPaused && count($this->connections) >= $this->limit) {
+ $this->autoPaused = true;
+
+ if (!$this->manuPaused) {
+ $this->server->pause();
+ }
+ }
+
+ $this->emit('connection', array($connection));
+ }
+
+ /** @internal */
+ public function handleDisconnection(ConnectionInterface $connection)
+ {
+ unset($this->connections[array_search($connection, $this->connections)]);
+
+ // continue accepting new connection if below limit
+ if ($this->autoPaused && count($this->connections) < $this->limit) {
+ $this->autoPaused = false;
+
+ if (!$this->manuPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ /** @internal */
+ public function handleError(Exception $error)
+ {
+ $this->emit('error', array($error));
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/SecureConnector.php b/assets/php/vendor/react/socket/src/SecureConnector.php
new file mode 100644
index 0000000..f04183d
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/SecureConnector.php
@@ -0,0 +1,64 @@
+connector = $connector;
+ $this->streamEncryption = new StreamEncryption($loop, false);
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ return Promise\reject(new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore
+ }
+
+ if (strpos($uri, '://') === false) {
+ $uri = 'tls://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $uri = str_replace('tls://', '', $uri);
+ $context = $this->context;
+
+ $encryption = $this->streamEncryption;
+ return $this->connector->connect($uri)->then(function (ConnectionInterface $connection) use ($context, $encryption) {
+ // (unencrypted) TCP/IP connection succeeded
+
+ if (!$connection instanceof Connection) {
+ $connection->close();
+ throw new UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource');
+ }
+
+ // set required SSL/TLS context options
+ foreach ($context as $name => $value) {
+ stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ // try to enable encryption
+ return $encryption->enable($connection)->then(null, function ($error) use ($connection) {
+ // establishing encryption failed => close invalid connection and return error
+ $connection->close();
+ throw $error;
+ });
+ });
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/SecureServer.php b/assets/php/vendor/react/socket/src/SecureServer.php
new file mode 100644
index 0000000..302ae93
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/SecureServer.php
@@ -0,0 +1,192 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+ *
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * Whenever a client fails to perform a successful TLS handshake, it will emit an
+ * `error` event and then close the underlying TCP/IP connection:
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'Error' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+ * If you want to typehint in your higher-level protocol implementation, you SHOULD
+ * use the generic `ServerInterface` instead.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class SecureServer extends EventEmitter implements ServerInterface
+{
+ private $tcp;
+ private $encryption;
+ private $context;
+
+ /**
+ * Creates a secure TLS server and starts waiting for incoming connections
+ *
+ * It does so by wrapping a `TcpServer` instance which waits for plaintext
+ * TCP/IP connections and then performs a TLS handshake for each connection.
+ * It thus requires valid [TLS context options],
+ * which in its most basic form may look something like this if you're using a
+ * PEM encoded certificate file:
+ *
+ * ```php
+ * $server = new TcpServer(8000, $loop);
+ * $server = new SecureServer($server, $loop, array(
+ * 'local_cert' => 'server.pem'
+ * ));
+ * ```
+ *
+ * Note that the certificate file will not be loaded on instantiation but when an
+ * incoming connection initializes its TLS context.
+ * This implies that any invalid certificate file paths or contents will only cause
+ * an `error` event at a later time.
+ *
+ * If your private key is encrypted with a passphrase, you have to specify it
+ * like this:
+ *
+ * ```php
+ * $server = new TcpServer(8000, $loop);
+ * $server = new SecureServer($server, $loop, array(
+ * 'local_cert' => 'server.pem',
+ * 'passphrase' => 'secret'
+ * ));
+ * ```
+ *
+ * Note that available [TLS context options],
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+ * you SHOULD pass a `TcpServer` instance as first parameter, unless you
+ * know what you're doing.
+ * Internally, the `SecureServer` has to set the required TLS context options on
+ * the underlying stream resources.
+ * These resources are not exposed through any of the interfaces defined in this
+ * package, but only through the internal `Connection` class.
+ * The `TcpServer` class is guaranteed to emit connections that implement
+ * the `ConnectionInterface` and uses the internal `Connection` class in order to
+ * expose these underlying resources.
+ * If you use a custom `ServerInterface` and its `connection` event does not
+ * meet this requirement, the `SecureServer` will emit an `error` event and
+ * then close the underlying connection.
+ *
+ * @param ServerInterface|TcpServer $tcp
+ * @param LoopInterface $loop
+ * @param array $context
+ * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
+ * @see TcpServer
+ * @link http://php.net/manual/en/context.ssl.php for TLS context options
+ */
+ public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context)
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ throw new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
+ }
+
+ // default to empty passphrase to suppress blocking passphrase prompt
+ $context += array(
+ 'passphrase' => ''
+ );
+
+ $this->tcp = $tcp;
+ $this->encryption = new StreamEncryption($loop);
+ $this->context = $context;
+
+ $that = $this;
+ $this->tcp->on('connection', function ($connection) use ($that) {
+ $that->handleConnection($connection);
+ });
+ $this->tcp->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ $address = $this->tcp->getAddress();
+ if ($address === null) {
+ return null;
+ }
+
+ return str_replace('tcp://' , 'tls://', $address);
+ }
+
+ public function pause()
+ {
+ $this->tcp->pause();
+ }
+
+ public function resume()
+ {
+ $this->tcp->resume();
+ }
+
+ public function close()
+ {
+ return $this->tcp->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ if (!$connection instanceof Connection) {
+ $this->emit('error', array(new UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
+ $connection->end();
+ return;
+ }
+
+ foreach ($this->context as $name => $value) {
+ stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ $that = $this;
+
+ $this->encryption->enable($connection)->then(
+ function ($conn) use ($that) {
+ $that->emit('connection', array($conn));
+ },
+ function ($error) use ($that, $connection) {
+ $that->emit('error', array($error));
+ $connection->end();
+ }
+ );
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/Server.php b/assets/php/vendor/react/socket/src/Server.php
new file mode 100644
index 0000000..72712e4
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/Server.php
@@ -0,0 +1,73 @@
+ $context);
+ }
+
+ // apply default options if not explicitly given
+ $context += array(
+ 'tcp' => array(),
+ 'tls' => array(),
+ 'unix' => array()
+ );
+
+ $scheme = 'tcp';
+ $pos = strpos($uri, '://');
+ if ($pos !== false) {
+ $scheme = substr($uri, 0, $pos);
+ }
+
+ if ($scheme === 'unix') {
+ $server = new UnixServer($uri, $loop, $context['unix']);
+ } else {
+ $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
+
+ if ($scheme === 'tls') {
+ $server = new SecureServer($server, $loop, $context['tls']);
+ }
+ }
+
+ $this->server = $server;
+
+ $that = $this;
+ $server->on('connection', function (ConnectionInterface $conn) use ($that) {
+ $that->emit('connection', array($conn));
+ });
+ $server->on('error', function (Exception $error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ $this->server->pause();
+ }
+
+ public function resume()
+ {
+ $this->server->resume();
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/ServerInterface.php b/assets/php/vendor/react/socket/src/ServerInterface.php
new file mode 100644
index 0000000..5319678
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/ServerInterface.php
@@ -0,0 +1,151 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'new connection' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ConnectionInterface` for more details about handling the
+ * incoming connection.
+ *
+ * error event:
+ * The `error` event will be emitted whenever there's an error accepting a new
+ * connection from a client.
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * Note that this is not a fatal error event, i.e. the server keeps listening for
+ * new connections even after this event.
+ *
+ * @see ConnectionInterface
+ */
+interface ServerInterface extends EventEmitterInterface
+{
+ /**
+ * Returns the full address (URI) this server is currently listening on
+ *
+ * ```php
+ * $address = $server->getAddress();
+ * echo 'Server listening on ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the address can not be determined or is unknown at this time (such as
+ * after the socket has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based server and you only want the local port, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $server->getAddress();
+ * $port = parse_url($address, PHP_URL_PORT);
+ * echo 'Server listening on port ' . $port . PHP_EOL;
+ * ```
+ *
+ * @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed)
+ */
+ public function getAddress();
+
+ /**
+ * Pauses accepting new incoming connections.
+ *
+ * Removes the socket resource from the EventLoop and thus stop accepting
+ * new connections. Note that the listening socket stays active and is not
+ * closed.
+ *
+ * This means that new incoming connections will stay pending in the
+ * operating system backlog until its configurable backlog is filled.
+ * Once the backlog is filled, the operating system may reject further
+ * incoming connections until the backlog is drained again by resuming
+ * to accept new connections.
+ *
+ * Once the server is paused, no futher `connection` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $server->pause();
+ *
+ * $server->on('connection', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * server MAY continue emitting `connection` events.
+ *
+ * Unless otherwise noted, a successfully opened server SHOULD NOT start
+ * in paused state.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes accepting new incoming connections.
+ *
+ * Re-attach the socket resource to the EventLoop after a previous `pause()`.
+ *
+ * ```php
+ * $server->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($server) {
+ * $server->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Shuts down this listening socket
+ *
+ * This will stop listening for new incoming connections on this socket.
+ *
+ * Calling this method more than once on the same instance is a NO-OP.
+ *
+ * @return void
+ */
+ public function close();
+}
diff --git a/assets/php/vendor/react/socket/src/StreamEncryption.php b/assets/php/vendor/react/socket/src/StreamEncryption.php
new file mode 100644
index 0000000..ba5d472
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/StreamEncryption.php
@@ -0,0 +1,146 @@
+loop = $loop;
+ $this->server = $server;
+
+ // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
+ // PHP 5.6+ supports bitmasks, legacy PHP only supports predefined
+ // constants, so apply accordingly below.
+ // Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did
+ // only support TLSv1.0, so we explicitly apply all versions.
+ // @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method
+ // @link https://3v4l.org/plbFn
+ if ($server) {
+ $this->method = STREAM_CRYPTO_METHOD_TLS_SERVER;
+
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_SERVER;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_SERVER;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_SERVER;
+ }
+ } else {
+ $this->method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
+
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+ }
+ }
+ }
+
+ public function enable(Connection $stream)
+ {
+ return $this->toggle($stream, true);
+ }
+
+ public function disable(Connection $stream)
+ {
+ return $this->toggle($stream, false);
+ }
+
+ public function toggle(Connection $stream, $toggle)
+ {
+ // pause actual stream instance to continue operation on raw stream socket
+ $stream->pause();
+
+ // TODO: add write() event to make sure we're not sending any excessive data
+
+ $deferred = new Deferred(function ($_, $reject) use ($toggle) {
+ // cancelling this leaves this stream in an inconsistent state…
+ $reject(new RuntimeException('Cancelled toggling encryption ' . $toggle ? 'on' : 'off'));
+ });
+
+ // get actual stream socket from stream instance
+ $socket = $stream->stream;
+
+ // get crypto method from context options or use global setting from constructor
+ $method = $this->method;
+ $context = stream_context_get_options($socket);
+ if (isset($context['ssl']['crypto_method'])) {
+ $method = $context['ssl']['crypto_method'];
+ }
+
+ $that = $this;
+ $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
+ $that->toggleCrypto($socket, $deferred, $toggle, $method);
+ };
+
+ $this->loop->addReadStream($socket, $toggleCrypto);
+
+ if (!$this->server) {
+ $toggleCrypto();
+ }
+
+ $loop = $this->loop;
+
+ return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) {
+ $loop->removeReadStream($socket);
+
+ $stream->encryptionEnabled = $toggle;
+ $stream->resume();
+
+ return $stream;
+ }, function($error) use ($stream, $socket, $loop) {
+ $loop->removeReadStream($socket);
+ $stream->resume();
+ throw $error;
+ });
+ }
+
+ public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
+ {
+ set_error_handler(array($this, 'handleError'));
+ $result = stream_socket_enable_crypto($socket, $toggle, $method);
+ restore_error_handler();
+
+ if (true === $result) {
+ $deferred->resolve();
+ } else if (false === $result) {
+ $deferred->reject(new UnexpectedValueException(
+ sprintf("Unable to complete SSL/TLS handshake: %s", $this->errstr),
+ $this->errno
+ ));
+ } else {
+ // need more data, will retry
+ }
+ }
+
+ public function handleError($errno, $errstr)
+ {
+ $this->errstr = str_replace(array("\r", "\n"), ' ', $errstr);
+ $this->errno = $errno;
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/TcpConnector.php b/assets/php/vendor/react/socket/src/TcpConnector.php
new file mode 100644
index 0000000..90d7df1
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/TcpConnector.php
@@ -0,0 +1,122 @@
+loop = $loop;
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $ip = trim($parts['host'], '[]');
+ if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP'));
+ }
+
+ // use context given in constructor
+ $context = array(
+ 'socket' => $this->context
+ );
+
+ // parse arguments from query component of URI
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // If an original hostname has been given, use this for TLS setup.
+ // This can happen due to layers of nested connectors, such as a
+ // DnsConnector reporting its original hostname.
+ // These context options are here in case TLS is enabled later on this stream.
+ // If TLS is not enabled later, this doesn't hurt either.
+ if (isset($args['hostname'])) {
+ $context['ssl'] = array(
+ 'SNI_enabled' => true,
+ 'peer_name' => $args['hostname']
+ );
+
+ // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
+ // The SNI_server_name context option has to be set here during construction,
+ // as legacy PHP ignores any values set later.
+ if (PHP_VERSION_ID < 50600) {
+ $context['ssl'] += array(
+ 'SNI_server_name' => $args['hostname'],
+ 'CN_match' => $args['hostname']
+ );
+ }
+ }
+
+ // latest versions of PHP no longer accept any other URI components and
+ // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
+ $remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
+
+ $socket = @stream_socket_client(
+ $remote,
+ $errno,
+ $errstr,
+ 0,
+ STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT,
+ stream_context_create($context)
+ );
+
+ if (false === $socket) {
+ return Promise\reject(new RuntimeException(
+ sprintf("Connection to %s failed: %s", $uri, $errstr),
+ $errno
+ ));
+ }
+
+ stream_set_blocking($socket, 0);
+
+ // wait for connection
+
+ return $this->waitForStreamOnce($socket);
+ }
+
+ private function waitForStreamOnce($stream)
+ {
+ $loop = $this->loop;
+
+ return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) {
+ $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) {
+ $loop->removeWriteStream($stream);
+
+ // The following hack looks like the only way to
+ // detect connection refused errors with PHP's stream sockets.
+ if (false === stream_socket_get_name($stream, true)) {
+ fclose($stream);
+
+ $reject(new RuntimeException('Connection refused'));
+ } else {
+ $resolve(new Connection($stream, $loop));
+ }
+ });
+ }, function () use ($loop, $stream) {
+ $loop->removeWriteStream($stream);
+ fclose($stream);
+
+ throw new RuntimeException('Cancelled while waiting for TCP/IP connection to be established');
+ });
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/TcpServer.php b/assets/php/vendor/react/socket/src/TcpServer.php
new file mode 100644
index 0000000..119e177
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/TcpServer.php
@@ -0,0 +1,236 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class TcpServer extends EventEmitter implements ServerInterface
+{
+ private $master;
+ private $loop;
+ private $listening = false;
+
+ /**
+ * Creates a plaintext TCP/IP socket server and starts listening on the given address
+ *
+ * This starts accepting new incoming connections on the given address.
+ * See also the `connection event` documented in the `ServerInterface`
+ * for more details.
+ *
+ * ```php
+ * $server = new TcpServer(8080, $loop);
+ * ```
+ *
+ * As above, the `$uri` parameter can consist of only a port, in which case the
+ * server will default to listening on the localhost address `127.0.0.1`,
+ * which means it will not be reachable from outside of this system.
+ *
+ * In order to use a random port assignment, you can use the port `0`:
+ *
+ * ```php
+ * $server = new TcpServer(0, $loop);
+ * $address = $server->getAddress();
+ * ```
+ *
+ * In order to change the host the socket is listening on, you can provide an IP
+ * address through the first parameter provided to the constructor, optionally
+ * preceded by the `tcp://` scheme:
+ *
+ * ```php
+ * $server = new TcpServer('192.168.0.1:8080', $loop);
+ * ```
+ *
+ * If you want to listen on an IPv6 address, you MUST enclose the host in square
+ * brackets:
+ *
+ * ```php
+ * $server = new TcpServer('[::1]:8080', $loop);
+ * ```
+ *
+ * If the given URI is invalid, does not contain a port, any other scheme or if it
+ * contains a hostname, it will throw an `InvalidArgumentException`:
+ *
+ * ```php
+ * // throws InvalidArgumentException due to missing port
+ * $server = new TcpServer('127.0.0.1', $loop);
+ * ```
+ *
+ * If the given URI appears to be valid, but listening on it fails (such as if port
+ * is already in use or port below 1024 may require root access etc.), it will
+ * throw a `RuntimeException`:
+ *
+ * ```php
+ * $first = new TcpServer(8080, $loop);
+ *
+ * // throws RuntimeException because port is already in use
+ * $second = new TcpServer(8080, $loop);
+ * ```
+ *
+ * Note that these error conditions may vary depending on your system and/or
+ * configuration.
+ * See the exception message and code for more details about the actual error
+ * condition.
+ *
+ * Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php)
+ * for the underlying stream socket resource like this:
+ *
+ * ```php
+ * $server = new TcpServer('[::1]:8080', $loop, array(
+ * 'backlog' => 200,
+ * 'so_reuseport' => true,
+ * 'ipv6_v6only' => true
+ * ));
+ * ```
+ *
+ * Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * @param string|int $uri
+ * @param LoopInterface $loop
+ * @param array $context
+ * @throws InvalidArgumentException if the listening address is invalid
+ * @throws RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($uri, LoopInterface $loop, array $context = array())
+ {
+ $this->loop = $loop;
+
+ // a single port has been given => assume localhost
+ if ((string)(int)$uri === (string)$uri) {
+ $uri = '127.0.0.1:' . $uri;
+ }
+
+ // assume default scheme if none has been given
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ // parse_url() does not accept null ports (random port assignment) => manually remove
+ if (substr($uri, -2) === ':0') {
+ $parts = parse_url(substr($uri, 0, -2));
+ if ($parts) {
+ $parts['port'] = 0;
+ }
+ } else {
+ $parts = parse_url($uri);
+ }
+
+ // ensure URI contains TCP scheme, host and port
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ throw new InvalidArgumentException('Invalid URI "' . $uri . '" given');
+ }
+
+ if (false === filter_var(trim($parts['host'], '[]'), FILTER_VALIDATE_IP)) {
+ throw new InvalidArgumentException('Given URI "' . $uri . '" does not contain a valid host IP');
+ }
+
+ $this->master = @stream_socket_server(
+ $uri,
+ $errno,
+ $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
+ stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ throw new RuntimeException('Failed to listen on "' . $uri . '": ' . $errstr, $errno);
+ }
+ stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!is_resource($this->master)) {
+ return null;
+ }
+
+ $address = stream_socket_get_name($this->master, false);
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = strrpos($address, ':');
+ if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
+ $port = substr($address, $pos + 1);
+ $address = '[' . substr($address, 0, $pos) . ']:' . $port;
+ }
+
+ return 'tcp://' . $address;
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ $newSocket = @stream_socket_accept($master);
+ if (false === $newSocket) {
+ $that->emit('error', array(new RuntimeException('Error accepting new connection')));
+
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $this->emit('connection', array(
+ new Connection($socket, $this->loop)
+ ));
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/TimeoutConnector.php b/assets/php/vendor/react/socket/src/TimeoutConnector.php
new file mode 100644
index 0000000..d4eba2e
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/TimeoutConnector.php
@@ -0,0 +1,25 @@
+connector = $connector;
+ $this->timeout = $timeout;
+ $this->loop = $loop;
+ }
+
+ public function connect($uri)
+ {
+ return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop);
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/UnixConnector.php b/assets/php/vendor/react/socket/src/UnixConnector.php
new file mode 100644
index 0000000..9b84ab0
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/UnixConnector.php
@@ -0,0 +1,44 @@
+loop = $loop;
+ }
+
+ public function connect($path)
+ {
+ if (strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (substr($path, 0, 7) !== 'unix://') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $path . '" is invalid'));
+ }
+
+ $resource = @stream_socket_client($path, $errno, $errstr, 1.0);
+
+ if (!$resource) {
+ return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno));
+ }
+
+ $connection = new Connection($resource, $this->loop);
+ $connection->unix = true;
+
+ return Promise\resolve($connection);
+ }
+}
diff --git a/assets/php/vendor/react/socket/src/UnixServer.php b/assets/php/vendor/react/socket/src/UnixServer.php
new file mode 100644
index 0000000..8f1ed98
--- /dev/null
+++ b/assets/php/vendor/react/socket/src/UnixServer.php
@@ -0,0 +1,130 @@
+loop = $loop;
+
+ if (strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (substr($path, 0, 7) !== 'unix://') {
+ throw new InvalidArgumentException('Given URI "' . $path . '" is invalid');
+ }
+
+ $this->master = @stream_socket_server(
+ $path,
+ $errno,
+ $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
+ stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ throw new RuntimeException('Failed to listen on unix domain socket "' . $path . '": ' . $errstr, $errno);
+ }
+ stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!is_resource($this->master)) {
+ return null;
+ }
+
+ return 'unix://' . stream_socket_get_name($this->master, false);
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ $newSocket = @stream_socket_accept($master);
+ if (false === $newSocket) {
+ $that->emit('error', array(new RuntimeException('Error accepting new connection')));
+
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $connection = new Connection($socket, $this->loop);
+ $connection->unix = true;
+
+ $this->emit('connection', array(
+ $connection
+ ));
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/ConnectionTest.php b/assets/php/vendor/react/socket/tests/ConnectionTest.php
new file mode 100644
index 0000000..d3563df
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/ConnectionTest.php
@@ -0,0 +1,47 @@
+markTestSkipped('HHVM does not support socket operation on test memory stream');
+ }
+
+ $resource = fopen('php://memory', 'r+');
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connection = new Connection($resource, $loop);
+ $connection->close();
+
+ $this->assertFalse(is_resource($resource));
+ }
+
+ public function testCloseConnectionWillRemoveResourceFromLoopBeforeClosingResource()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not support socket operation on test memory stream');
+ }
+
+ $resource = fopen('php://memory', 'r+');
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($resource);
+
+ $onRemove = null;
+ $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($param) use (&$onRemove) {
+ $onRemove = is_resource($param);
+ return true;
+ }));
+
+ $connection = new Connection($resource, $loop);
+ $connection->write('test');
+ $connection->close();
+
+ $this->assertTrue($onRemove);
+ $this->assertFalse(is_resource($resource));
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/ConnectorTest.php b/assets/php/vendor/react/socket/tests/ConnectorTest.php
new file mode 100644
index 0000000..c8eb19b
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/ConnectorTest.php
@@ -0,0 +1,128 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp
+ ));
+
+ $connector->connect('127.0.0.1:80');
+ }
+
+ public function testConnectorPassedThroughHostnameIfDnsIsDisabled()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('tcp://google.com:80')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'dns' => false
+ ));
+
+ $connector->connect('tcp://google.com:80');
+ }
+
+ public function testConnectorWithUnknownSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop);
+
+ $promise = $connector->connect('unknown://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTcpDefaultSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tcp' => false
+ ));
+
+ $promise = $connector->connect('google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTcpSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tcp' => false
+ ));
+
+ $promise = $connector->connect('tcp://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTlsSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tls' => false
+ ));
+
+ $promise = $connector->connect('tls://google.com:443');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledUnixSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'unix' => false
+ ));
+
+ $promise = $connector->connect('unix://demo.sock');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorUsesGivenResolverInstance()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+ $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'dns' => $resolver
+ ));
+
+ $connector->connect('google.com:80');
+ }
+
+ public function testConnectorUsesResolvedHostnameIfDnsIsUsed()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function ($resolve) { $resolve('127.0.0.1'); });
+ $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+ $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise);
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('tcp://127.0.0.1:80?hostname=google.com')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'dns' => $resolver
+ ));
+
+ $connector->connect('tcp://google.com:80');
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/DnsConnectorTest.php b/assets/php/vendor/react/socket/tests/DnsConnectorTest.php
new file mode 100644
index 0000000..3c94c39
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/DnsConnectorTest.php
@@ -0,0 +1,111 @@
+tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+
+ $this->connector = new DnsConnector($this->tcp, $this->resolver);
+ }
+
+ public function testPassByResolverIfGivenIp()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('127.0.0.1:80');
+ }
+
+ public function testPassThroughResolverIfGivenHost()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('google.com:80');
+ }
+
+ public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('::1')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('google.com:80');
+ }
+
+ public function testPassByResolverIfGivenCompleteUri()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment');
+ }
+
+ public function testPassThroughResolverIfGivenCompleteUri()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://google.com:80/path?query#fragment');
+ }
+
+ public function testPassThroughResolverIfGivenExplicitHost()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://google.com:80/?hostname=google.de');
+ }
+
+ public function testRejectsImmediatelyIfUriIsInvalid()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('////');
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testSkipConnectionIfDnsFails()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject()));
+ $this->tcp->expects($this->never())->method('connect');
+
+ $this->connector->connect('example.invalid:80');
+ }
+
+ public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, $this->expectCallableOnce());
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue($pending));
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testCancelDuringTcpConnectionCancelsTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/FixedUriConnectorTest.php b/assets/php/vendor/react/socket/tests/FixedUriConnectorTest.php
new file mode 100644
index 0000000..f42d74f
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/FixedUriConnectorTest.php
@@ -0,0 +1,19 @@
+getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $base->expects($this->once())->method('connect')->with('test')->willReturn('ret');
+
+ $connector = new FixedUriConnector('test', $base);
+
+ $this->assertEquals('ret', $connector->connect('ignored'));
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/FunctionalConnectorTest.php b/assets/php/vendor/react/socket/tests/FunctionalConnectorTest.php
new file mode 100644
index 0000000..6611352
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/FunctionalConnectorTest.php
@@ -0,0 +1,32 @@
+on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new Connector($loop);
+
+ $connection = Block\await($connector->connect('localhost:9998'), $loop, self::TIMEOUT);
+
+ $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
+
+ $connection->close();
+ $server->close();
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/FunctionalSecureServerTest.php b/assets/php/vendor/react/socket/tests/FunctionalSecureServerTest.php
new file mode 100644
index 0000000..78a59d0
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/FunctionalSecureServerTest.php
@@ -0,0 +1,438 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+ }
+
+ public function testEmitsConnectionForNewConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testWritesDataToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write('foo');
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local ConnectionInterface */
+
+ $local->on('data', $this->expectCallableOnceWith('foo'));
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testWritesDataInMultipleChunksToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write(str_repeat('*', 400000));
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ public function testWritesMoreDataInMultipleChunksToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write(str_repeat('*', 2000000));
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(2000000, $received);
+ }
+
+ public function testEmitsDataFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $once = $this->expectCallableOnceWith('foo');
+ $server->on('connection', function (ConnectionInterface $conn) use ($once) {
+ $conn->on('data', $once);
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $local->write("foo");
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsDataInMultipleChunksFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $received = 0;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$received) {
+ $conn->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $local->write(str_repeat('*', 400000));
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ public function testPipesDataBackInMultipleChunksFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) use (&$received) {
+ $conn->pipe($conn);
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ $local->write(str_repeat('*', 400000));
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testEmitsConnectionForNewTlsv11Connection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false,
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testEmitsErrorForClientWithTlsVersionMismatch()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false,
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsConnectionForNewConnectionWithEncryptedCertificate()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem',
+ 'passphrase' => 'swordfish'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithInvalidCertificate()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'invalid.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithEncryptedCertificateMissingPassphrase()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithEncryptedCertificateWithInvalidPassphrase()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem',
+ 'passphrase' => 'nope'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForConnectionWithPeerVerification()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => true
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then(null, $this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsErrorIfConnectionIsCancelled()
+ {
+ if (PHP_OS !== 'Linux') {
+ $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsNothingIfConnectionIsIdle()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableNever());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect(str_replace('tls://', '', $server->getAddress()));
+
+ $promise->then($this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsErrorIfConnectionIsNotSecureHandshake()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect(str_replace('tls://', '', $server->getAddress()));
+
+ $promise->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/FunctionalTcpServerTest.php b/assets/php/vendor/react/socket/tests/FunctionalTcpServerTest.php
new file mode 100644
index 0000000..ec7855e
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/FunctionalTcpServerTest.php
@@ -0,0 +1,324 @@
+on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsNoConnectionForNewConnectionWhenPaused()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+ $server->pause();
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionForNewConnectionWhenResumedAfterPause()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->pause();
+ $server->resume();
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionWithRemoteIp()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $peer);
+ }
+
+ public function testEmitsConnectionWithLocalIp()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $local);
+ $this->assertEquals($server->getAddress(), $local);
+ }
+
+ public function testEmitsConnectionWithLocalIpDespiteListeningOnAll()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer('0.0.0.0:0', $loop);
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $local);
+ }
+
+ public function testEmitsConnectionWithRemoteIpAfterConnectionIsClosedByPeer()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $conn->on('close', function () use ($conn, &$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $client = Block\await($promise, $loop, 0.1);
+ $client->end();
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $peer);
+ }
+
+ public function testEmitsConnectionWithRemoteNullAddressAfterConnectionIsClosedLocally()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $conn->close();
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertNull($peer);
+ }
+
+ public function testEmitsConnectionEvenIfConnectionIsCancelled()
+ {
+ if (PHP_OS !== 'Linux') {
+ $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionForNewIpv6Connection()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionWithRemoteIpv6()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('[::1]:', $peer);
+ }
+
+ public function testEmitsConnectionWithLocalIpv6()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('[::1]:', $local);
+ $this->assertEquals($server->getAddress(), $local);
+ }
+
+ public function testEmitsConnectionWithInheritedContextOptions()
+ {
+ if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
+ // https://3v4l.org/hB4Tc
+ $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop, array(
+ 'backlog' => 4
+ ));
+
+ $all = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$all) {
+ $all = stream_context_get_options($conn->stream);
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnInvalidUri()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('///', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWithoutPort()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('127.0.0.1', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWithWrongScheme()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('udp://127.0.0.1:0', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWIthHostname()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('localhost:8080', $loop);
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/IntegrationTest.php b/assets/php/vendor/react/socket/tests/IntegrationTest.php
new file mode 100644
index 0000000..24dbe37
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/IntegrationTest.php
@@ -0,0 +1,171 @@
+connect('google.com:80'), $loop);
+
+ $this->assertContains(':80', $conn->getRemoteAddress());
+ $this->assertNotEquals('google.com:80', $conn->getRemoteAddress());
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingEncryptedStuffFromGoogleShouldWork()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+ $secureConnector = new Connector($loop);
+
+ $conn = Block\await($secureConnector->connect('tls://google.com:443'), $loop);
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingEncryptedStuffFromGoogleShouldWorkIfHostIsResolvedFirst()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $factory = new ResolverFactory();
+ $dns = $factory->create('8.8.8.8', $loop);
+
+ $connector = new DnsConnector(
+ new SecureConnector(
+ new TcpConnector($loop),
+ $loop
+ ),
+ $dns
+ );
+
+ $conn = Block\await($connector->connect('google.com:443'), $loop);
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingPlaintextStuffFromEncryptedGoogleShouldNotWork()
+ {
+ $loop = Factory::create();
+ $connector = new Connector($loop);
+
+ $conn = Block\await($connector->connect('google.com:443'), $loop);
+
+ $this->assertContains(':443', $conn->getRemoteAddress());
+ $this->assertNotEquals('google.com:443', $conn->getRemoteAddress());
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertNotRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ public function testConnectingFailsIfDnsUsesInvalidResolver()
+ {
+ $loop = Factory::create();
+
+ $factory = new ResolverFactory();
+ $dns = $factory->create('demo.invalid', $loop);
+
+ $connector = new Connector($loop, array(
+ 'dns' => $dns
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
+ }
+
+ public function testConnectingFailsIfTimeoutIsTooSmall()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'timeout' => 0.001
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
+ }
+
+ public function testSelfSignedRejectsIfVerificationIsEnabled()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'verify_peer' => true
+ )
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
+ }
+
+ public function testSelfSignedResolvesIfVerificationIsDisabled()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'verify_peer' => false
+ )
+ ));
+
+ $conn = Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
+ $conn->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/LimitingServerTest.php b/assets/php/vendor/react/socket/tests/LimitingServerTest.php
new file mode 100644
index 0000000..2cc9a58
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/LimitingServerTest.php
@@ -0,0 +1,195 @@
+getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn('127.0.0.1:1234');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $this->assertEquals('127.0.0.1:1234', $server->getAddress());
+ }
+
+ public function testPauseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ }
+
+ public function testPauseTwiceWillBePassedThroughToTcpServerOnce()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testResumeWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->resume();
+ }
+
+ public function testResumeTwiceWillBePassedThroughToTcpServerOnce()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->resume();
+ $server->resume();
+ }
+
+ public function testCloseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('close');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->close();
+ }
+
+ public function testSocketErrorWillBeForwarded()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('error', array(new \RuntimeException('test')));
+ }
+
+ public function testSocketConnectionWillBeForwarded()
+ {
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 100);
+ $server->on('connection', $this->expectCallableOnceWith($connection));
+ $server->on('error', $this->expectCallableNever());
+
+ $tcp->emit('connection', array($connection));
+
+ $this->assertEquals(array($connection), $server->getConnections());
+ }
+
+ public function testSocketConnectionWillBeClosedOnceLimitIsReached()
+ {
+ $first = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $first->expects($this->never())->method('close');
+ $second = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $second->expects($this->once())->method('close');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 1);
+ $server->on('connection', $this->expectCallableOnceWith($first));
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('connection', array($first));
+ $tcp->emit('connection', array($second));
+ }
+
+ public function testPausingServerWillBePausedOnceLimitIsReached()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $tcp = new TcpServer(0, $loop);
+
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $server = new LimitingServer($tcp, 1, true);
+
+ $tcp->emit('connection', array($connection));
+ }
+
+ public function testSocketDisconnectionWillRemoveFromList()
+ {
+ $loop = Factory::create();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $socket = stream_socket_client($tcp->getAddress());
+ fclose($socket);
+
+ $server = new LimitingServer($tcp, 100);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('error', $this->expectCallableNever());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array(), $server->getConnections());
+ }
+
+ public function testPausingServerWillEmitOnlyOneButAcceptTwoConnectionsDueToOperatingSystem()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new LimitingServer($server, 1, true);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('error', $this->expectCallableNever());
+
+ $first = stream_socket_client($server->getAddress());
+ $second = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ fclose($first);
+ fclose($second);
+ }
+
+ public function testPausingServerWillEmitTwoConnectionsFromBacklog()
+ {
+ $loop = Factory::create();
+
+ $twice = $this->createCallableMock();
+ $twice->expects($this->exactly(2))->method('__invoke');
+
+ $server = new TcpServer(0, $loop);
+ $server = new LimitingServer($server, 1, true);
+ $server->on('connection', $twice);
+ $server->on('error', $this->expectCallableNever());
+
+ $first = stream_socket_client($server->getAddress());
+ fclose($first);
+ $second = stream_socket_client($server->getAddress());
+ fclose($second);
+
+ Block\sleep(0.1, $loop);
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/SecureConnectorTest.php b/assets/php/vendor/react/socket/tests/SecureConnectorTest.php
new file mode 100644
index 0000000..0b3a702
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/SecureConnectorTest.php
@@ -0,0 +1,74 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->connector = new SecureConnector($this->tcp, $this->loop);
+ }
+
+ public function testConnectionWillWaitForTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectionWithCompleteUriWillBePassedThroughExpectForScheme()
+ {
+ $pending = new Promise\Promise(function () { });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80/path?query#fragment'))->will($this->returnValue($pending));
+
+ $this->connector->connect('tls://example.com:80/path?query#fragment');
+ }
+
+ public function testConnectionToInvalidSchemeWillReject()
+ {
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('tcp://example.com:80');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testCancelDuringTcpConnectionCancelsTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testConnectionWillBeClosedAndRejectedIfConnectioIsNoStream()
+ {
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $connection->expects($this->once())->method('close');
+
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->willReturn(Promise\resolve($connection));
+
+ $promise = $this->connector->connect('example.com:80');
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/SecureIntegrationTest.php b/assets/php/vendor/react/socket/tests/SecureIntegrationTest.php
new file mode 100644
index 0000000..8c9ba14
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/SecureIntegrationTest.php
@@ -0,0 +1,204 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $this->loop = LoopFactory::create();
+ $this->server = new TcpServer(0, $this->loop);
+ $this->server = new SecureServer($this->server, $this->loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $this->address = $this->server->getAddress();
+ $this->connector = new SecureConnector(new TcpConnector($this->loop), $this->loop, array('verify_peer' => false));
+ }
+
+ public function tearDown()
+ {
+ if ($this->server !== null) {
+ $this->server->close();
+ $this->server = null;
+ }
+ }
+
+ public function testConnectToServer()
+ {
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testConnectToServerEmitsConnection()
+ {
+ $promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce());
+
+ $promiseClient = $this->connector->connect($this->address);
+
+ list($_, $client) = Block\awaitAll(array($promiseServer, $promiseClient), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->close();
+ }
+
+ public function testSendSmallDataToServerReceivesOneChunk()
+ {
+ // server expects one connection which emits one data event
+ $received = new Deferred();
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($received) {
+ $peer->on('data', function ($chunk) use ($received) {
+ $received->resolve($chunk);
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->write('hello');
+
+ // await server to report one "data" event
+ $data = Block\await($received->promise(), $this->loop, self::TIMEOUT);
+
+ $client->close();
+
+ $this->assertEquals('hello', $data);
+ }
+
+ public function testSendDataWithEndToServerReceivesAllData()
+ {
+ $disconnected = new Deferred();
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($disconnected) {
+ $received = '';
+ $peer->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ $peer->on('close', function () use (&$received, $disconnected) {
+ $disconnected->resolve($received);
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $data = str_repeat('a', 200000);
+ $client->end($data);
+
+ // await server to report connection "close" event
+ $received = Block\await($disconnected->promise(), $this->loop, self::TIMEOUT);
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testSendDataWithoutEndingToServerReceivesAllData()
+ {
+ $received = '';
+ $this->server->on('connection', function (ConnectionInterface $peer) use (&$received) {
+ $peer->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $data = str_repeat('d', 200000);
+ $client->write($data);
+
+ // buffer incoming data for 0.1s (should be plenty of time)
+ Block\sleep(0.1, $this->loop);
+
+ $client->close();
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testConnectToServerWhichSendsSmallDataReceivesOneChunk()
+ {
+ $this->server->on('connection', function (ConnectionInterface $peer) {
+ $peer->write('hello');
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // await client to report one "data" event
+ $receive = $this->createPromiseForEvent($client, 'data', $this->expectCallableOnceWith('hello'));
+ Block\await($receive, $this->loop, self::TIMEOUT);
+
+ $client->close();
+ }
+
+ public function testConnectToServerWhichSendsDataWithEndReceivesAllData()
+ {
+ $data = str_repeat('b', 100000);
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
+ $peer->end($data);
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // await data from client until it closes
+ $received = $this->buffer($client, $this->loop, self::TIMEOUT);
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData()
+ {
+ $data = str_repeat('c', 100000);
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
+ $peer->write($data);
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // buffer incoming data for 0.1s (should be plenty of time)
+ $received = '';
+ $client->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ Block\sleep(0.1, $this->loop);
+
+ $client->close();
+
+ $this->assertEquals($data, $received);
+ }
+
+ private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $fn)
+ {
+ return new Promise(function ($resolve) use ($emitter, $event, $fn) {
+ $emitter->on($event, function () use ($resolve, $fn) {
+ $resolve(call_user_func_array($fn, func_get_args()));
+ });
+ });
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/SecureServerTest.php b/assets/php/vendor/react/socket/tests/SecureServerTest.php
new file mode 100644
index 0000000..92c641f
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/SecureServerTest.php
@@ -0,0 +1,105 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+ }
+
+ public function testGetAddressWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn('tcp://127.0.0.1:1234');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $this->assertEquals('tls://127.0.0.1:1234', $server->getAddress());
+ }
+
+ public function testGetAddressWillReturnNullIfTcpServerReturnsNull()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn(null);
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $this->assertNull($server->getAddress());
+ }
+
+ public function testPauseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->pause();
+ }
+
+ public function testResumeWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->resume();
+ }
+
+ public function testCloseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('close');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->close();
+ }
+
+ public function testConnectionWillBeEndedWithErrorIfItIsNotAStream()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $connection->expects($this->once())->method('end');
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('connection', array($connection));
+ }
+
+ public function testSocketErrorWillBeForwarded()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('error', array(new \RuntimeException('test')));
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/ServerTest.php b/assets/php/vendor/react/socket/tests/ServerTest.php
new file mode 100644
index 0000000..14fdb2c
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/ServerTest.php
@@ -0,0 +1,173 @@
+assertNotEquals(0, $server->getAddress());
+ $server->close();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsForInvalidUri()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new Server('invalid URI', $loop);
+ }
+
+ public function testConstructorCreatesExpectedTcpServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+
+ $connector = new TcpConnector($loop);
+ $connector->connect($server->getAddress())
+ ->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT);
+
+ $connection->close();
+ $server->close();
+ }
+
+ public function testConstructorCreatesExpectedUnixServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server($this->getRandomSocketUri(), $loop);
+
+ $connector = new UnixConnector($loop);
+ $connector->connect($server->getAddress())
+ ->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT);
+
+ $connection->close();
+ $server->close();
+ }
+
+ public function testEmitsConnectionForNewConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesNotEmitConnectionForNewConnectionToPausedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->pause();
+ $server->on('connection', $this->expectCallableNever());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesEmitConnectionForNewConnectionToResumedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->pause();
+ $server->on('connection', $this->expectCallableOnce());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ $server->resume();
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesNotAllowConnectionToClosedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+ $address = $server->getAddress();
+ $server->close();
+
+ $client = @stream_socket_client($address);
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertFalse($client);
+ }
+
+ public function testEmitsConnectionWithInheritedContextOptions()
+ {
+ if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
+ // https://3v4l.org/hB4Tc
+ $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
+ }
+
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop, array(
+ 'backlog' => 4
+ ));
+
+ $all = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$all) {
+ $all = stream_context_get_options($conn->stream);
+ });
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
+ }
+
+ public function testDoesNotEmitSecureConnectionForNewPlainConnection()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $server = new Server('tls://127.0.0.1:0', $loop, array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ )
+ ));
+ $server->on('connection', $this->expectCallableNever());
+
+ $client = stream_socket_client(str_replace('tls://', '', $server->getAddress()));
+
+ Block\sleep(0.1, $loop);
+ }
+
+ private function getRandomSocketUri()
+ {
+ return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/Stub/CallableStub.php b/assets/php/vendor/react/socket/tests/Stub/CallableStub.php
new file mode 100644
index 0000000..1b197eb
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/Stub/CallableStub.php
@@ -0,0 +1,10 @@
+data .= $data;
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ }
+
+ public function close()
+ {
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getRemoteAddress()
+ {
+ return '127.0.0.1';
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/Stub/ServerStub.php b/assets/php/vendor/react/socket/tests/Stub/ServerStub.php
new file mode 100644
index 0000000..d9e74f4
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/Stub/ServerStub.php
@@ -0,0 +1,18 @@
+connect('127.0.0.1:9999')
+ ->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldAddResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $valid = false;
+ $loop->expects($this->once())->method('addWriteStream')->with($this->callback(function ($arg) use (&$valid) {
+ $valid = is_resource($arg);
+ return true;
+ }));
+ $connector->connect($server->getAddress());
+
+ $this->assertTrue($valid);
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceed()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+
+ $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithRemoteAdressSameAsTarget()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertEquals('tcp://127.0.0.1:9999', $connection->getRemoteAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithLocalAdressOnLocalhost()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertContains('tcp://127.0.0.1:', $connection->getLocalAddress());
+ $this->assertNotEquals('tcp://127.0.0.1:9999', $connection->getLocalAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithNullAddressesAfterConnectionClosed()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $connection->close();
+
+ $this->assertNull($connection->getRemoteAddress());
+ $this->assertNull($connection->getLocalAddress());
+ }
+
+ /** @test */
+ public function connectionToTcpServerWillCloseWhenOtherSideCloses()
+ {
+ $loop = Factory::create();
+
+ // immediately close connection and server once connection is in
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', function (ConnectionInterface $conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $once = $this->expectCallableOnce();
+ $connector = new TcpConnector($loop);
+ $connector->connect($server->getAddress())->then(function (ConnectionInterface $conn) use ($once) {
+ $conn->write('hello');
+ $conn->on('close', $once);
+ });
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToEmptyIp6PortShouldFail()
+ {
+ $loop = Factory::create();
+
+ $connector = new TcpConnector($loop);
+ $connector
+ ->connect('[::1]:9999')
+ ->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToIp6TcpServerShouldSucceed()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:9999', $loop);
+ } catch (\Exception $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (IPv6 not supported on this system?)');
+ }
+
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('[::1]:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertEquals('tcp://[::1]:9999', $connection->getRemoteAddress());
+
+ $this->assertContains('tcp://[::1]:', $connection->getLocalAddress());
+ $this->assertNotEquals('tcp://[::1]:9999', $connection->getLocalAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToHostnameShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('www.google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function connectionToInvalidPortShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('255.255.255.255:12345678')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function connectionToInvalidSchemeShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('tls://google.com:443')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function cancellingConnectionShouldRemoveResourceFromLoopAndCloseResource()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $loop->expects($this->once())->method('addWriteStream');
+ $promise = $connector->connect($server->getAddress());
+
+ $resource = null;
+ $valid = false;
+ $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($arg) use (&$resource, &$valid) {
+ $resource = $arg;
+ $valid = is_resource($arg);
+ return true;
+ }));
+ $promise->cancel();
+
+ $this->assertTrue($valid);
+ $this->assertFalse(is_resource($resource));
+ }
+
+ /** @test */
+ public function cancellingConnectionShouldRejectPromise()
+ {
+ $loop = Factory::create();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $this->setExpectedException('RuntimeException', 'Cancelled');
+ Block\await($promise, $loop);
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/TcpServerTest.php b/assets/php/vendor/react/socket/tests/TcpServerTest.php
new file mode 100644
index 0000000..72b3c28
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/TcpServerTest.php
@@ -0,0 +1,285 @@
+loop = $this->createLoop();
+ $this->server = new TcpServer(0, $this->loop);
+
+ $this->port = parse_url($this->server->getAddress(), PHP_URL_PORT);
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::handleConnection
+ */
+ public function testConnection()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $this->server->on('connection', $this->expectCallableOnce());
+
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::handleConnection
+ */
+ public function testConnectionWithManyClients()
+ {
+ $client1 = stream_socket_client('tcp://localhost:'.$this->port);
+ $client2 = stream_socket_client('tcp://localhost:'.$this->port);
+ $client3 = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $this->server->on('connection', $this->expectCallableExactly(3));
+ $this->tick();
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataEventWillNotBeEmittedWhenClientSendsNoData()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedWithDataClientSends()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ fwrite($client, "foo\n");
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+ fwrite($client, "foo\n");
+ stream_socket_shutdown($client, STREAM_SHUT_WR);
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testLoopWillEndWhenServerIsClosed()
+ {
+ // explicitly unset server because we already call close()
+ $this->server->close();
+ $this->server = null;
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testCloseTwiceIsNoOp()
+ {
+ $this->server->close();
+ $this->server->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testGetAddressAfterCloseReturnsNull()
+ {
+ $this->server->close();
+ $this->assertNull($this->server->getAddress());
+ }
+
+ public function testLoopWillEndWhenServerIsClosedAfterSingleConnection()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $server->on('connection', function ($conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+ $stream = new DuplexResourceStream($client, $this->loop);
+
+ $bytes = 1024 * 1024;
+ $stream->end(str_repeat('*', $bytes));
+
+ $mock = $this->expectCallableOnce();
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $received = 0;
+ $server->on('connection', function ($conn) use ($mock, &$received, $server) {
+ // count number of bytes received
+ $conn->on('data', function ($data) use (&$received) {
+ $received += strlen($data);
+ });
+
+ $conn->on('end', $mock);
+
+ // do not await any further connections in order to let the loop terminate
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ $this->assertEquals($bytes, $received);
+ }
+
+ public function testConnectionDoesNotEndWhenClientDoesNotClose()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\Connection::end
+ */
+ public function testConnectionDoesEndWhenClientCloses()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ fclose($client);
+
+ $mock = $this->expectCallableOnce();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testCtorAddsResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new TcpServer(0, $loop);
+ }
+
+ public function testResumeWithoutPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->resume();
+ }
+
+ public function testPauseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->pause();
+ }
+
+ public function testPauseAfterPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testCloseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testListenOnBusyPortThrows()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Windows supports listening on same port multiple times');
+ }
+
+ $another = new TcpServer($this->port, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::close
+ */
+ public function tearDown()
+ {
+ if ($this->server) {
+ $this->server->close();
+ }
+ }
+
+ private function tick()
+ {
+ Block\sleep(0, $this->loop);
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/TestCase.php b/assets/php/vendor/react/socket/tests/TestCase.php
new file mode 100644
index 0000000..e87fc2f
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/TestCase.php
@@ -0,0 +1,101 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Socket\Stub\CallableStub')->getMock();
+ }
+
+ protected function buffer(ReadableStreamInterface $stream, LoopInterface $loop, $timeout)
+ {
+ if (!$stream->isReadable()) {
+ return '';
+ }
+
+ return Block\await(new Promise(
+ function ($resolve, $reject) use ($stream) {
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $reject);
+
+ $stream->on('close', function () use (&$buffer, $resolve) {
+ $resolve($buffer);
+ });
+ },
+ function () use ($stream) {
+ $stream->close();
+ throw new \RuntimeException();
+ }
+ ), $loop, $timeout);
+ }
+
+ public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
+ {
+ if (method_exists($this, 'expectException')) {
+ // PHPUnit 5+
+ $this->expectException($exception);
+ if ($exceptionMessage !== '') {
+ $this->expectExceptionMessage($exceptionMessage);
+ }
+ if ($exceptionCode !== null) {
+ $this->expectExceptionCode($exceptionCode);
+ }
+ } else {
+ // legacy PHPUnit 4
+ parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
+ }
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/TimeoutConnectorTest.php b/assets/php/vendor/react/socket/tests/TimeoutConnectorTest.php
new file mode 100644
index 0000000..64787d9
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/TimeoutConnectorTest.php
@@ -0,0 +1,103 @@
+getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testRejectsWhenConnectorRejects()
+ {
+ $promise = Promise\reject(new \RuntimeException());
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 5.0, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testResolvesWhenConnectorResolves()
+ {
+ $promise = Promise\resolve();
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 5.0, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableOnce(),
+ $this->expectCallableNever()
+ );
+
+ $loop->run();
+ }
+
+ public function testRejectsAndCancelsPendingPromiseOnTimeout()
+ {
+ $promise = new Promise\Promise(function () { }, $this->expectCallableOnce());
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testCancelsPendingPromiseOnCancel()
+ {
+ $promise = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $out = $timeout->connect('google.com:80');
+ $out->cancel();
+
+ $out->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/UnixConnectorTest.php b/assets/php/vendor/react/socket/tests/UnixConnectorTest.php
new file mode 100644
index 0000000..1564064
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/UnixConnectorTest.php
@@ -0,0 +1,64 @@
+loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->connector = new UnixConnector($this->loop);
+ }
+
+ public function testInvalid()
+ {
+ $promise = $this->connector->connect('google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testInvalidScheme()
+ {
+ $promise = $this->connector->connect('tcp://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testValid()
+ {
+ // random unix domain socket path
+ $path = sys_get_temp_dir() . '/test' . uniqid() . '.sock';
+
+ // temporarily create unix domain socket server to connect to
+ $server = stream_socket_server('unix://' . $path, $errno, $errstr);
+
+ // skip test if we can not create a test server (Windows etc.)
+ if (!$server) {
+ $this->markTestSkipped('Unable to create socket "' . $path . '": ' . $errstr . '(' . $errno .')');
+ return;
+ }
+
+ // tests succeeds if we get notified of successful connection
+ $promise = $this->connector->connect($path);
+ $promise->then($this->expectCallableOnce());
+
+ // remember remote and local address of this connection and close again
+ $remote = $local = false;
+ $promise->then(function(ConnectionInterface $conn) use (&$remote, &$local) {
+ $remote = $conn->getRemoteAddress();
+ $local = $conn->getLocalAddress();
+ $conn->close();
+ });
+
+ // clean up server
+ fclose($server);
+ unlink($path);
+
+ $this->assertNull($local);
+ $this->assertEquals('unix://' . $path, $remote);
+ }
+}
diff --git a/assets/php/vendor/react/socket/tests/UnixServerTest.php b/assets/php/vendor/react/socket/tests/UnixServerTest.php
new file mode 100644
index 0000000..10f7e4f
--- /dev/null
+++ b/assets/php/vendor/react/socket/tests/UnixServerTest.php
@@ -0,0 +1,283 @@
+loop = Factory::create();
+ $this->uds = $this->getRandomSocketUri();
+ $this->server = new UnixServer($this->uds, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::handleConnection
+ */
+ public function testConnection()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $this->server->on('connection', $this->expectCallableOnce());
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::handleConnection
+ */
+ public function testConnectionWithManyClients()
+ {
+ $client1 = stream_socket_client($this->uds);
+ $client2 = stream_socket_client($this->uds);
+ $client3 = stream_socket_client($this->uds);
+
+ $this->server->on('connection', $this->expectCallableExactly(3));
+ $this->tick();
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataEventWillNotBeEmittedWhenClientSendsNoData()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedWithDataClientSends()
+ {
+ $client = stream_socket_client($this->uds);
+
+ fwrite($client, "foo\n");
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending()
+ {
+ $client = stream_socket_client($this->uds);
+ fwrite($client, "foo\n");
+ stream_socket_shutdown($client, STREAM_SHUT_WR);
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testLoopWillEndWhenServerIsClosed()
+ {
+ // explicitly unset server because we already call close()
+ $this->server->close();
+ $this->server = null;
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testCloseTwiceIsNoOp()
+ {
+ $this->server->close();
+ $this->server->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testGetAddressAfterCloseReturnsNull()
+ {
+ $this->server->close();
+ $this->assertNull($this->server->getAddress());
+ }
+
+ public function testLoopWillEndWhenServerIsClosedAfterSingleConnection()
+ {
+ $client = stream_socket_client($this->uds);
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $server->on('connection', function ($conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts()
+ {
+ $client = stream_socket_client($this->uds);
+ $stream = new DuplexResourceStream($client, $this->loop);
+
+ $bytes = 1024 * 1024;
+ $stream->end(str_repeat('*', $bytes));
+
+ $mock = $this->expectCallableOnce();
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $received = 0;
+ $server->on('connection', function ($conn) use ($mock, &$received, $server) {
+ // count number of bytes received
+ $conn->on('data', function ($data) use (&$received) {
+ $received += strlen($data);
+ });
+
+ $conn->on('end', $mock);
+
+ // do not await any further connections in order to let the loop terminate
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ $this->assertEquals($bytes, $received);
+ }
+
+ public function testConnectionDoesNotEndWhenClientDoesNotClose()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\Connection::end
+ */
+ public function testConnectionDoesEndWhenClientCloses()
+ {
+ $client = stream_socket_client($this->uds);
+
+ fclose($client);
+
+ $mock = $this->expectCallableOnce();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testCtorAddsResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ }
+
+ public function testResumeWithoutPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->resume();
+ }
+
+ public function testPauseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->pause();
+ }
+
+ public function testPauseAfterPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testCloseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testListenOnBusyPortThrows()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Windows supports listening on same port multiple times');
+ }
+
+ $another = new UnixServer($this->uds, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::close
+ */
+ public function tearDown()
+ {
+ if ($this->server) {
+ $this->server->close();
+ }
+ }
+
+ private function getRandomSocketUri()
+ {
+ return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
+ }
+
+ private function tick()
+ {
+ Block\sleep(0, $this->loop);
+ }
+}
diff --git a/assets/php/vendor/react/stream/.gitignore b/assets/php/vendor/react/stream/.gitignore
new file mode 100644
index 0000000..987e2a2
--- /dev/null
+++ b/assets/php/vendor/react/stream/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/assets/php/vendor/react/stream/.travis.yml b/assets/php/vendor/react/stream/.travis.yml
new file mode 100644
index 0000000..f4e3376
--- /dev/null
+++ b/assets/php/vendor/react/stream/.travis.yml
@@ -0,0 +1,50 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+# - 7.0 # Mac OS X test setup, ignore errors, see below
+ - 7.1
+ - 7.2
+ - nightly # ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ include:
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: nightly
+ - php: hhvm
+ - os: osx
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
+ - time php examples/91-benchmark-throughput.php
diff --git a/assets/php/vendor/react/stream/CHANGELOG.md b/assets/php/vendor/react/stream/CHANGELOG.md
new file mode 100644
index 0000000..f64815d
--- /dev/null
+++ b/assets/php/vendor/react/stream/CHANGELOG.md
@@ -0,0 +1,377 @@
+# Changelog
+
+## 0.7.7 (2018-01-19)
+
+* Improve test suite by fixing forward compatibility with upcoming EventLoop
+ releases, avoid risky tests and add test group to skip integration tests
+ relying on internet connection and apply appropriate test timeouts.
+ (#128, #131 and #132 by @clue)
+
+## 0.7.6 (2017-12-21)
+
+* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12
+ (#126 by @clue)
+
+* Improve test suite by simplifying test bootstrapping logic via Composer and
+ test against PHP 7.2
+ (#127 by @clue and #124 by @carusogabriel)
+
+## 0.7.5 (2017-11-20)
+
+* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream`
+ (#119 by @clue)
+
+* Fix: Fix forward compatibility with upcoming EventLoop releases
+ (#121 by @clue)
+
+* Restructure examples to ease getting started
+ (#123 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#122 by @gabriel-caruso and #120 by @clue)
+
+## 0.7.4 (2017-10-11)
+
+* Fix: Remove event listeners from `CompositeStream` once closed and
+ remove undocumented left-over `close` event argument
+ (#116 by @clue)
+
+* Minor documentation improvements: Fix wrong class name in example,
+ fix typos in README and
+ fix forward compatibility with upcoming EventLoop releases in example
+ (#113 by @docteurklein and #114 and #115 by @clue)
+
+* Improve test suite by running against Mac OS X on Travis
+ (#112 by @clue)
+
+## 0.7.3 (2017-08-05)
+
+* Improvement: Support Événement 3.0 a long side 2.0 and 1.0
+ (#108 by @WyriHaximus)
+
+* Readme: Corrected loop initialization in usage example
+ (#109 by @pulyavin)
+
+* Travis: Lock linux distribution preventing future builds from breaking
+ (#110 by @clue)
+
+## 0.7.2 (2017-06-15)
+
+* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream.
+ (#107 by @WyriHaximus)
+
+## 0.7.1 (2017-05-20)
+
+* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of
+ bytes to write at once.
+ (#105 by @clue)
+
+ ```php
+ $stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+ ```
+
+* Ignore HHVM test failures for now until Travis tests work again
+ (#106 by @clue)
+
+## 0.7.0 (2017-05-04)
+
+* Removed / BC break: Remove deprecated and unneeded functionality
+ (#45, #87, #90, #91 and #93 by @clue)
+
+ * Remove deprecated `Stream` class, use `DuplexResourceStream` instead
+ (#87 by @clue)
+
+ * Remove public `$buffer` property, use new constructor parameters instead
+ (#91 by @clue)
+
+ * Remove public `$stream` property from all resource streams
+ (#90 by @clue)
+
+ * Remove undocumented and now unused `ReadableStream` and `WritableStream`
+ (#93 by @clue)
+
+ * Remove `BufferedSink`
+ (#45 by @clue)
+
+* Feature / BC break: Simplify `ThroughStream` by using data callback instead of
+ inheritance. It is now a direct implementation of `DuplexStreamInterface`.
+ (#88 and #89 by @clue)
+
+ ```php
+ $through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+ });
+ $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+ $through->write(array(2, true));
+ ```
+
+* Feature / BC break: The `CompositeStream` starts closed if either side is
+ already closed and forwards pause to pipe source on first write attempt.
+ (#96 and #103 by @clue)
+
+ If either side of the composite stream closes, it will also close the other
+ side. We now also ensure that if either side is already closed during
+ instantiation, it will also close the other side.
+
+* BC break: Mark all classes as `final` and
+ mark internal API as `private` to discourage inheritance
+ (#95 and #99 by @clue)
+
+* Feature / BC break: Only emit `error` event for fatal errors
+ (#92 by @clue)
+
+ > The `error` event was previously also allowed to be emitted for non-fatal
+ errors, but our implementations actually only ever emitted this as a fatal
+ error and then closed the stream.
+
+* Feature: Explicitly allow custom events and exclude any semantics
+ (#97 by @clue)
+
+* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation
+ (#100 and #102 by @clue)
+
+* Actually require all dependencies so this is self-contained and improve
+ forward compatibility with EventLoop v1.0 and v0.5
+ (#94 and #98 by @clue)
+
+## 0.6.0 (2017-03-26)
+
+* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream`
+ (#85 by @clue)
+
+ ```php
+ // old (does still work for BC reasons)
+ $stream = new Stream($connection, $loop);
+
+ // new
+ $stream = new DuplexResourceStream($connection, $loop);
+ ```
+
+ Note that the `DuplexResourceStream` now rejects read-only or write-only
+ streams, so this may affect BC. If you want a read-only or write-only
+ resource, use `ReadableResourceStream` or `WritableResourceStream` instead of
+ `DuplexResourceStream`.
+
+ > BC note: This class was previously called `Stream`. The `Stream` class still
+ exists for BC reasons and will be removed in future versions of this package.
+
+* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`)
+ (#84 by @clue)
+
+ ```php
+ // old
+ $stream = new Buffer(STDOUT, $loop);
+
+ // new
+ $stream = new WritableResourceStream(STDOUT, $loop);
+ ```
+
+* Feature: Add `ReadableResourceStream`
+ (#83 by @clue)
+
+ ```php
+ $stream = new ReadableResourceStream(STDIN, $loop);
+ ```
+
+* Fix / BC Break: Enforce using non-blocking I/O
+ (#46 by @clue)
+
+ > BC note: This is known to affect process pipes on Windows which do not
+ support non-blocking I/O and could thus block the whole EventLoop previously.
+
+* Feature / Fix / BC break: Consistent semantics for
+ `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side
+ (#86 by @clue)
+
+* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4
+ (#80 by @clue)
+
+## 0.5.0 (2017-03-08)
+
+* Feature / BC break: Consistent `end` event semantics (EOF)
+ (#70 by @clue)
+
+ The `end` event will now only be emitted for a *successful* end, not if the
+ stream closes due to an unrecoverable `error` event or if you call `close()`
+ explicitly.
+ If you want to detect when the stream closes (terminates), use the `close`
+ event instead.
+
+* BC break: Remove custom (undocumented) `full-drain` event from `Buffer`
+ (#63 and #68 by @clue)
+
+ > The `full-drain` event was undocumented and mostly used internally.
+ Relying on this event has attracted some low-quality code in the past, so
+ we've removed this from the public API in order to work out a better
+ solution instead.
+ If you want to detect when the buffer finishes flushing data to the stream,
+ you may want to look into its `end()` method or the `close` event instead.
+
+* Feature / BC break: Consistent event semantics and documentation,
+ explicitly state *when* events will be emitted and *which* arguments they
+ receive.
+ (#73 and #69 by @clue)
+
+ The documentation now explicitly defines each event and its arguments.
+ Custom events and event arguments are still supported.
+ Most notably, all defined events only receive inherently required event
+ arguments and no longer transmit the instance they are emitted on for
+ consistency and performance reasons.
+
+ ```php
+ // old (inconsistent and not supported by all implementations)
+ $stream->on('data', function ($data, $stream) {
+ // process $data
+ });
+
+ // new (consistent throughout the whole ecosystem)
+ $stream->on('data', function ($data) use ($stream) {
+ // process $data
+ });
+ ```
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature / BC break: Consistent method semantics and documentation
+ (#72 by @clue)
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature: Consistent `pipe()` semantics for closed and closing streams
+ (#71 from @clue)
+
+ The source stream will now always be paused via `pause()` when the
+ destination stream closes. Also, properly stop piping if the source
+ stream closes and remove all event forwarding.
+
+* Improve test suite by adding PHPUnit to `require-dev` and improving coverage.
+ (#74 and #75 by @clue, #66 by @nawarian)
+
+## 0.4.6 (2017-01-25)
+
+* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone)
+ (#62 by @clue)
+
+* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream`
+ (#60 by @clue)
+
+* Fix: Consistent `close` event behavior for `Buffer`
+ (#61 by @clue)
+
+## 0.4.5 (2016-11-13)
+
+* Feature: Support setting read buffer size to `null` (infinite)
+ (#42 by @clue)
+
+* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event
+ (#55 by @clue)
+
+* Vastly improved performance by factor of 10x to 20x.
+ Raise default buffer sizes to 64 KiB and simplify and improve error handling
+ and unneeded function calls.
+ (#53, #55, #56 by @clue)
+
+## 0.4.4 (2016-08-22)
+
+* Bug fix: Emit `error` event and close `Stream` when accessing the underlying
+ stream resource fails with a permanent error.
+ (#52 and #40 by @clue, #25 by @lysenkobv)
+
+* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF)
+ (#39 by @clue)
+
+* Bug fix: Ignore empty writes to `Buffer`
+ (#51 by @clue)
+
+* Add benchmarking script to measure throughput in CI
+ (#41 by @clue)
+
+## 0.4.3 (2015-10-07)
+
+* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau)
+* Bug fix: No double-write during drain call (@arnaud-lb)
+* Bug fix: Support HHVM (@clue)
+* Adjust compatibility to 5.3 (@clue)
+
+## 0.4.2 (2014-09-09)
+
+* Added DuplexStreamInterface
+* Stream sets stream resources to non-blocking
+* Fixed potential race condition in pipe
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: [Stream] Correctly detect closed connections
+
+## 0.3.2 (2013-05-10)
+
+* Bug fix: [Stream] Make sure CompositeStream is closed properly
+
+## 0.3.1 (2013-04-21)
+
+* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()`
+
+## 0.3.0 (2013-04-14)
+
+* Feature: [Stream] Factory method for BufferedSink
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.5 (2012-11-26)
+
+* Feature: Make BufferedSink trigger progress events on the promise (@jsor)
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream
+* Feature: Added BufferedSink
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Version bump
+
+## 0.2.1 (2012-10-14)
+
+* Bug fix: Check for EOF in `Buffer::write()`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/assets/php/vendor/react/stream/LICENSE b/assets/php/vendor/react/stream/LICENSE
new file mode 100644
index 0000000..a808108
--- /dev/null
+++ b/assets/php/vendor/react/stream/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+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.
diff --git a/assets/php/vendor/react/stream/README.md b/assets/php/vendor/react/stream/README.md
new file mode 100644
index 0000000..c362534
--- /dev/null
+++ b/assets/php/vendor/react/stream/README.md
@@ -0,0 +1,1224 @@
+# Stream
+
+[](https://travis-ci.org/reactphp/stream)
+
+Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/).
+
+In order to make the [EventLoop](https://github.com/reactphp/event-loop)
+easier to use, this component introduces the powerful concept of "streams".
+Streams allow you to efficiently process huge amounts of data (such as a multi
+Gigabyte file download) in small chunks without having to store everything in
+memory at once.
+They are very similar to the streams found in PHP itself,
+but have an interface more suited for async, non-blocking I/O.
+
+**Table of contents**
+
+* [Stream usage](#stream-usage)
+ * [ReadableStreamInterface](#readablestreaminterface)
+ * [data event](#data-event)
+ * [end event](#end-event)
+ * [error event](#error-event)
+ * [close event](#close-event)
+ * [isReadable()](#isreadable)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [pipe()](#pipe)
+ * [close()](#close)
+ * [WritableStreamInterface](#writablestreaminterface)
+ * [drain event](#drain-event)
+ * [pipe event](#pipe-event)
+ * [error event](#error-event-1)
+ * [close event](#close-event-1)
+ * [isWritable()](#iswritable)
+ * [write()](#write)
+ * [end()](#end)
+ * [close()](#close-1)
+ * [DuplexStreamInterface](#duplexstreaminterface)
+* [Creating streams](#creating-streams)
+ * [ReadableResourceStream](#readableresourcestream)
+ * [WritableResourceStream](#writableresourcestream)
+ * [DuplexResourceStream](#duplexresourcestream)
+ * [ThroughStream](#throughstream)
+ * [CompositeStream](#compositestream)
+* [Usage](#usage)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Stream usage
+
+ReactPHP uses the concept of "streams" throughout its ecosystem to provide a
+consistent higher-level abstraction for processing streams of arbitrary data
+contents and size.
+While a stream itself is a quite low-level concept, it can be used as a powerful
+abstraction to build higher-level components and protocols on top.
+
+If you're new to this concept, it helps to think of them as a water pipe:
+You can consume water from a source or you can produce water and forward (pipe)
+it to any destination (sink).
+
+Similarly, streams can either be
+
+* readable (such as `STDIN` terminal input) or
+* writable (such as `STDOUT` terminal output) or
+* duplex (both readable *and* writable, such as a TCP/IP connection)
+
+Accordingly, this package defines the following three interfaces
+
+* [`ReadableStreamInterface`](#readablestreaminterface)
+* [`WritableStreamInterface`](#writablestreaminterface)
+* [`DuplexStreamInterface`](#duplexstreaminterface)
+
+### ReadableStreamInterface
+
+The `ReadableStreamInterface` is responsible for providing an interface for
+read-only streams and the readable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### data event
+
+The `data` event will be emitted whenever some data was read/received
+from this source stream.
+The event receives a single mixed argument for incoming data.
+
+```php
+$stream->on('data', function ($data) {
+ echo $data;
+});
+```
+
+This event MAY be emitted any number of times, which may be zero times if
+this stream does not send any data at all.
+It SHOULD not be emitted after an `end` or `close` event.
+
+The given `$data` argument may be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit the raw (binary) payload data that is received over the wire as
+chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end event
+
+The `end` event will be emitted once the source stream has successfully
+reached the end of the stream (EOF).
+
+```php
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+a successful end was detected.
+It SHOULD NOT be emitted after a previous `end` or `close` event.
+It MUST NOT be emitted if the stream closes due to a non-successful
+end, such as after a previous `error` event.
+
+After the stream is ended, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+This event will only be emitted if the *end* was reached successfully,
+not if the stream was interrupted by an unrecoverable error or explicitly
+closed. Not all streams know this concept of a "successful end".
+Many use-cases involve detecting when the stream closes (terminates)
+instead, in this case you should use the `close` event.
+After the stream emits an `end` event, it SHOULD usually be followed by a
+`close` event.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit this event if either the remote side closes the connection or
+a file handle was successfully read until reaching its end (EOF).
+
+Note that this event should not be confused with the `end()` method.
+This event defines a successful end *reading* from a source stream, while
+the `end()` method defines *writing* a successful end to a destination
+stream.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to read from this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error or after an unexpected `data` or premature
+`end` event.
+It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-readable mode, see
+also `close()` and `isReadable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and do not make assumption about data
+boundaries (such as unexpected `data` or premature `end` events).
+In other words, many lower-level protocols (such as TCP/IP) may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+Unlike the `end` event, this event SHOULD be emitted whenever the stream
+closes, irrespective of whether this happens implicitly due to an
+unrecoverable error or explicitly when either side closes the stream.
+If you only want to detect a *successful* end, you should use the `end`
+event instead.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after reading a *successful* `end`
+event or after a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isReadable()
+
+The `isReadable(): bool` method can be used to
+check whether this stream is in a readable state (not closed already).
+
+This method can be used to check if the stream still accepts incoming
+data events or if it is ended or closed already.
+Once the stream is non-readable, no further `data` or `end` events SHOULD
+be emitted.
+
+```php
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+A successfully opened stream always MUST start in readable mode.
+
+Once the stream ends or closes, it MUST switch to non-readable mode.
+This can happen any time, explicitly through `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-readable mode, it MUST NOT transition
+back to readable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `isWritable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause reading incoming data events.
+
+Removes the data source file descriptor from the event loop. This
+allows you to throttle incoming data.
+
+Unless otherwise noted, a successfully opened stream SHOULD NOT start
+in paused state.
+
+Once the stream is paused, no futher `data` or `end` events SHOULD
+be emitted.
+
+```php
+$stream->pause();
+
+$stream->on('data', assertShouldNeverCalled());
+$stream->on('end', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+stream MAY continue emitting `data` events.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+
+See also `resume()`.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume reading incoming data events.
+
+Re-attach the data source after a previous `pause()`.
+
+```php
+$stream->pause();
+
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+
+See also `pause()`.
+
+#### pipe()
+
+The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to
+pipe all the data from this readable source into the given writable destination.
+
+Automatically sends all incoming data to the destination.
+Automatically throttles the source based on what the destination can handle.
+
+```php
+$source->pipe($dest);
+```
+
+Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+into itself in order to write back all the data that is received.
+This may be a useful feature for a TCP/IP echo service:
+
+```php
+$connection->pipe($connection);
+```
+
+This method returns the destination stream as-is, which can be used to
+set up chains of piped streams:
+
+```php
+$source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+```
+
+By default, this will call `end()` on the destination stream once the
+source stream emits an `end` event. This can be disabled like this:
+
+```php
+$source->pipe($dest, array('end' => false));
+```
+
+Note that this only applies to the `end` event.
+If an `error` or explicit `close` event happens on the source stream,
+you'll have to manually close the destination stream:
+
+```php
+$source->pipe($dest);
+$source->on('close', function () use ($dest) {
+ $dest->end('BYE!');
+});
+```
+
+If the source stream is not readable (closed state), then this is a NO-OP.
+
+```php
+$source->close();
+$source->pipe($dest); // NO-OP
+```
+
+If the destinantion stream is not writable (closed state), then this will simply
+throttle (pause) the source stream:
+
+```php
+$dest->close();
+$source->pipe($dest); // calls $source->pause()
+```
+
+Similarly, if the destination stream is closed while the pipe is still
+active, it will also throttle (pause) the source stream:
+
+```php
+$source->pipe($dest);
+$dest->close(); // calls $source->pause()
+```
+
+Once the pipe is set up successfully, the destination stream MUST emit
+a `pipe` event with this source stream an event argument.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to (forcefully) close the stream.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-readable
+mode, see also `isReadable()`.
+This means that no further `data` or `end` events SHOULD be emitted.
+
+```php
+$stream->close();
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this method should not be confused with the `end()` method.
+
+### WritableStreamInterface
+
+The `WritableStreamInterface` is responsible for providing an interface for
+write-only streams and the writable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### drain event
+
+The `drain` event will be emitted whenever the write buffer became full
+previously and is now ready to accept more data.
+
+```php
+$stream->on('drain', function () use ($stream) {
+ echo 'Stream is now ready to accept more data';
+});
+```
+
+This event SHOULD be emitted once every time the buffer became full
+previously and is now ready to accept more data.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if the buffer never became full in the first place.
+This event SHOULD NOT be emitted if the buffer has not become full
+previously.
+
+This event is mostly used internally, see also `write()` for more details.
+
+#### pipe event
+
+The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+into this stream.
+The event receives a single `ReadableStreamInterface` argument for the
+source stream.
+
+```php
+$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ echo 'Now receiving piped data';
+
+ // explicitly close target if source emits an error
+ $source->on('error', function () use ($stream) {
+ $stream->close();
+ });
+});
+
+$source->pipe($stream);
+```
+
+This event MUST be emitted once for each readable stream that is
+successfully piped into this destination stream.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if no stream is ever piped into this stream.
+This event MUST NOT be emitted if either the source is not readable
+(closed already) or this destination is not writable (closed already).
+
+This event is mostly used internally, see also `pipe()` for more details.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to write to this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$stream->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error.
+It SHOULD NOT be emitted after a previous `error` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-writable mode, see
+also `close()` and `isWritable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-writable mode,
+see also `isWritable()`.
+
+This event SHOULD be emitted whenever the stream closes, irrespective of
+whether this happens implicitly due to an unrecoverable error or
+explicitly when either side closes the stream.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after flushing the buffer from
+the `end()` method, after receiving a *successful* `end` event or after
+a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isWritable()
+
+The `isWritable(): bool` method can be used to
+check whether this stream is in a writable state (not closed already).
+
+This method can be used to check if the stream still accepts writing
+any data or if it is ended or closed already.
+Writing any data to a non-writable stream is a NO-OP:
+
+```php
+assert($stream->isWritable() === false);
+
+$stream->write('end'); // NO-OP
+$stream->end('end'); // NO-OP
+```
+
+A successfully opened stream always MUST start in writable mode.
+
+Once the stream ends or closes, it MUST switch to non-writable mode.
+This can happen any time, explicitly through `end()` or `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-writable mode, it MUST NOT transition
+back to writable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `isReadable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### write()
+
+The `write(mixed $data): bool` method can be used to
+write some data into the stream.
+
+A successful write MUST be confirmed with a boolean `true`, which means
+that either the data was written (flushed) immediately or is buffered and
+scheduled for a future write. Note that this interface gives you no
+control over explicitly flushing the buffered data, as finding the
+appropriate time for this is beyond the scope of this interface and left
+up to the implementation of this interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+an `error` event and MAY `close()` the stream if it can not recover from
+this error.
+
+If the internal buffer is full after adding `$data`, then `write()`
+SHOULD return `false`, indicating that the caller should stop sending
+data until the buffer drains.
+The stream SHOULD send a `drain` event once the buffer is ready to accept
+more data.
+
+Similarly, if the the stream is not writable (already in a closed state)
+it MUST NOT process the given `$data` and SHOULD return `false`,
+indicating that the caller should stop sending data.
+
+The given `$data` argument MAY be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will only accept the raw (binary) payload data that is transferred over
+the wire as chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end()
+
+The `end(mixed $data = null): void` method can be used to
+successfully end the stream (after optionally sending some final data).
+
+This method can be used to successfully end the stream, i.e. close
+the stream after sending out all data that is currently buffered.
+
+```php
+$stream->write('hello');
+$stream->write('world');
+$stream->end();
+```
+
+If there's no data currently buffered and nothing to be flushed, then
+this method MAY `close()` the stream immediately.
+
+If there's still data in the buffer that needs to be flushed first, then
+this method SHOULD try to write out this data and only then `close()`
+the stream.
+Once the stream is closed, it SHOULD emit a `close` event.
+
+Note that this interface gives you no control over explicitly flushing
+the buffered data, as finding the appropriate time for this is beyond the
+scope of this interface and left up to the implementation of this
+interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+You can optionally pass some final data that is written to the stream
+before ending the stream. If a non-`null` value is given as `$data`, then
+this method will behave just like calling `write($data)` before ending
+with no data.
+
+```php
+// shorter version
+$stream->end('bye');
+
+// same as longer version
+$stream->write('bye');
+$stream->end();
+```
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->end();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+also end its readable side, unless the stream supports half-open mode.
+In other words, after calling this method, these streams SHOULD switch
+into non-writable AND non-readable mode, see also `isReadable()`.
+This implies that in this case, the stream SHOULD NOT emit any `data`
+or `end` events anymore.
+Streams MAY choose to use the `pause()` method logic for this, but
+special care may have to be taken to ensure a following call to the
+`resume()` method SHOULD NOT continue emitting readable events.
+
+Note that this method should not be confused with the `close()` method.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to forcefully close the stream, i.e. close
+the stream without waiting for any buffered data to be flushed.
+If there's still data in the buffer, this data SHOULD be discarded.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->close();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+Note that this method should not be confused with the `end()` method.
+Unlike the `end()` method, this method does not take care of any existing
+buffers and simply discards any buffer contents.
+Likewise, this method may also be called after calling `end()` on a
+stream in order to stop waiting for the stream to flush its final data.
+
+```php
+$stream->end();
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->close();
+});
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+
+### DuplexStreamInterface
+
+The `DuplexStreamInterface` is responsible for providing an interface for
+duplex streams (both readable and writable).
+
+It builds on top of the existing interfaces for readable and writable streams
+and follows the exact same method and event semantics.
+If you're new to this concept, you should look into the
+`ReadableStreamInterface` and `WritableStreamInterface` first.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to the same events defined
+on the `ReadbleStreamInterface` and `WritableStreamInterface`.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+## Creating streams
+
+ReactPHP uses the concept of "streams" throughout its ecosystem, so that
+many higher-level consumers of this package only deal with
+[stream usage](#stream-usage).
+This implies that stream instances are most often created within some
+higher-level components and many consumers never actually have to deal with
+creating a stream instance.
+
+* Use [react/socket](https://github.com/reactphp/socket)
+ if you want to accept incoming or establish outgoing plaintext TCP/IP or
+ secure TLS socket connection streams.
+* Use [react/http](https://github.com/reactphp/http)
+ if you want to receive an incoming HTTP request body streams.
+* Use [react/child-process](https://github.com/reactphp/child-process)
+ if you want to communicate with child processes via process pipes such as
+ STDIN, STDOUT, STDERR etc.
+* Use experimental [react/filesystem](https://github.com/reactphp/filesystem)
+ if you want to read from / write to the filesystem.
+* See also the last chapter for [more real-world applications](#more).
+
+However, if you are writing a lower-level component or want to create a stream
+instance from a stream resource, then the following chapter is for you.
+
+> Note that the following examples use `fopen()` and `stream_socket_client()`
+ for illustration purposes only.
+ These functions SHOULD NOT be used in a truly async program because each call
+ may take several seconds to complete and would block the EventLoop otherwise.
+ Additionally, the `fopen()` call will return a file handle on some platforms
+ which may or may not be supported by all EventLoop implementations.
+ As an alternative, you may want to use higher-level libraries listed above.
+
+### ReadableResourceStream
+
+The `ReadableResourceStream` is a concrete implementation of the
+[`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-only resource like a file stream opened in
+readable mode or a stream such as `STDIN`:
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop);
+$stream->on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened in reading mode (e.g. `fopen()` mode `r`).
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new ReadableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDIN etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new ReadableResourceStream(STDIN, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop, 8192);
+```
+
+> PHP bug warning: If the PHP process has explicitly been started without a
+ `STDIN` stream, then trying to read from `STDIN` may return data from
+ another stream resource. This does not happen if you start this with an empty
+ stream like `php test.php < /dev/null` instead of `php test.php <&-`.
+ See [#81](https://github.com/reactphp/stream/issues/81) for more details.
+
+### WritableResourceStream
+
+The `WritableResourceStream` is a concrete implementation of the
+[`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a write-only resource like a file stream opened in
+writable mode or a stream such as `STDOUT` or `STDERR`:
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new WritableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new WritableResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls
+this maximum buffer size in bytes.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, 8192);
+```
+
+This class takes an optional `int|null $writeChunkSize` parameter that controls
+this maximum buffer size in bytes to write at once to the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be written
+at once to the underlying stream resource. Note that the actual number
+of bytes written may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "write everything available" to the
+underlying stream resource.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+```
+
+See also [`write()`](#write) for more details.
+
+### DuplexResourceStream
+
+The `DuplexResourceStream` is a concrete implementation of the
+[`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-and-write resource like a file stream opened
+in read and write mode mode or a stream such as a TCP/IP connection:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for reading *and* writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new DuplexResourceStream(false, $loop);
+```
+
+See also the [`ReadableResourceStream`](#readableresourcestream) for read-only
+and the [`WritableResourceStream`](#writableresourcestream) for write-only
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new DuplexResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop, 8192);
+```
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes another optional `WritableStreamInterface|null $buffer` parameter
+that controls this write behavior of this stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+If you want to change the write buffer soft limit, you can pass an instance of
+[`WritableResourceStream`](#writableresourcestream) like this:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$buffer = new WritableResourceStream($conn, $loop, 8192);
+$stream = new DuplexResourceStream($conn, $loop, null, $buffer);
+```
+
+See also [`WritableResourceStream`](#writableresourcestream) for more details.
+
+### ThroughStream
+
+The `ThroughStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
+you write to it through to its readable end.
+
+```php
+$through = new ThroughStream();
+$through->on('data', $this->expectCallableOnceWith('hello'));
+
+$through->write('hello');
+```
+
+Similarly, the [`end()` method](#end) will end the stream and emit an
+[`end` event](#end-event) and then [`close()`](#close-1) the stream.
+The [`close()` method](#close-1) will close the stream and emit a
+[`close` event](#close-event).
+Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+
+```php
+$through = new ThroughStream();
+$source->pipe($through)->pipe($dest);
+```
+
+Optionally, its constructor accepts any callable function which will then be
+used to *filter* any data written to it. This function receives a single data
+argument as passed to the writable side and must return the data as it will be
+passed to its readable end:
+
+```php
+$through = new ThroughStream('strtoupper');
+$source->pipe($through)->pipe($dest);
+```
+
+Note that this class makes no assumptions about any data types. This can be
+used to convert data, for example for transforming any structured data into
+a newline-delimited JSON (NDJSON) stream like this:
+
+```php
+$through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+});
+$through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+$through->write(array(2, true));
+```
+
+The callback function is allowed to throw an `Exception`. In this case,
+the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+
+```php
+$through = new ThroughStream(function ($data) {
+ if (!is_string($data)) {
+ throw new \UnexpectedValueException('Only strings allowed');
+ }
+ return $data;
+});
+$through->on('error', $this->expectCallableOnce()));
+$through->on('close', $this->expectCallableOnce()));
+$through->on('data', $this->expectCallableNever()));
+
+$through->write(2);
+```
+
+### CompositeStream
+
+The `CompositeStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a
+single duplex stream from two individual streams implementing
+[`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) respectively.
+
+This is useful for some APIs which may require a single
+[`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often
+more convenient to work with a single stream instance like this:
+
+```php
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$stdio = new CompositeStream($stdin, $stdout);
+
+$stdio->on('data', function ($chunk) use ($stdio) {
+ $stdio->write('You said: ' . $chunk);
+});
+```
+
+This is a well-behaving stream which forwards all stream events from the
+underlying streams and forwards all streams calls to the underlying streams.
+
+If you `write()` to the duplex stream, it will simply `write()` to the
+writable side and return its status.
+
+If you `end()` the duplex stream, it will `end()` the writable side and will
+`pause()` the readable side.
+
+If you `close()` the duplex stream, both input streams will be closed.
+If either of the two input streams emits a `close` event, the duplex stream
+will also close.
+If either of the two input streams is already closed while constructing the
+duplex stream, it will `close()` the other side and return a closed stream.
+
+## Usage
+
+The following example can be used to pipe the contents of a source file into
+a destination file without having to ever read the whole file into memory:
+
+```php
+$loop = new React\EventLoop\StreamSelectLoop;
+
+$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r'), $loop);
+$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w'), $loop);
+
+$source->pipe($dest);
+
+$loop->run();
+```
+
+> Note that this example uses `fopen()` for illustration purposes only.
+ This should not be used in a truly async program because the filesystem is
+ inherently blocking and each call could potentially take several seconds.
+ See also [creating streams](#creating-streams) for more sophisticated
+ examples.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/stream:^0.7.7
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## More
+
+* See [creating streams](#creating-streams) for more information on how streams
+ are created in real-world applications.
+* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
+ [dependents on Packagist](https://packagist.org/packages/react/stream/dependents)
+ for a list of packages that use streams in real-world applications.
diff --git a/assets/php/vendor/react/stream/composer.json b/assets/php/vendor/react/stream/composer.json
new file mode 100644
index 0000000..f6faa66
--- /dev/null
+++ b/assets/php/vendor/react/stream/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "react/stream",
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35",
+ "clue/stream-filter": "~1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Stream\\": "tests"
+ }
+ }
+}
diff --git a/assets/php/vendor/react/stream/examples/01-http.php b/assets/php/vendor/react/stream/examples/01-http.php
new file mode 100644
index 0000000..3687f7c
--- /dev/null
+++ b/assets/php/vendor/react/stream/examples/01-http.php
@@ -0,0 +1,40 @@
+on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/assets/php/vendor/react/stream/examples/02-https.php b/assets/php/vendor/react/stream/examples/02-https.php
new file mode 100644
index 0000000..163f7c8
--- /dev/null
+++ b/assets/php/vendor/react/stream/examples/02-https.php
@@ -0,0 +1,40 @@
+on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/assets/php/vendor/react/stream/examples/11-cat.php b/assets/php/vendor/react/stream/examples/11-cat.php
new file mode 100644
index 0000000..90fadc0
--- /dev/null
+++ b/assets/php/vendor/react/stream/examples/11-cat.php
@@ -0,0 +1,28 @@
+pipe($stdout);
+
+$loop->run();
diff --git a/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php b/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php
new file mode 100644
index 0000000..ecf695c
--- /dev/null
+++ b/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php
@@ -0,0 +1,62 @@
+write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL);
+}
+$info->write('piping from ' . $if . ' to ' . $of . ' (for max ' . $t . ' second(s)) ...'. PHP_EOL);
+
+// setup input and output streams and pipe inbetween
+$fh = fopen($if, 'r');
+$in = new React\Stream\ReadableResourceStream($fh, $loop);
+$out = new React\Stream\WritableResourceStream(fopen($of, 'w'), $loop);
+$in->pipe($out);
+
+// stop input stream in $t seconds
+$start = microtime(true);
+$timeout = $loop->addTimer($t, function () use ($in, &$bytes) {
+ $in->close();
+});
+
+// print stream position once stream closes
+$in->on('close', function () use ($fh, $start, $loop, $timeout, $info) {
+ $t = microtime(true) - $start;
+ $loop->cancelTimer($timeout);
+
+ $bytes = ftell($fh);
+
+ $info->write('read ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL);
+ $info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL);
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/stream/phpunit.xml.dist b/assets/php/vendor/react/stream/phpunit.xml.dist
new file mode 100644
index 0000000..13d3fab
--- /dev/null
+++ b/assets/php/vendor/react/stream/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/assets/php/vendor/react/stream/src/CompositeStream.php b/assets/php/vendor/react/stream/src/CompositeStream.php
new file mode 100644
index 0000000..153f2a3
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/CompositeStream.php
@@ -0,0 +1,82 @@
+readable = $readable;
+ $this->writable = $writable;
+
+ if (!$readable->isReadable() || !$writable->isWritable()) {
+ return $this->close();
+ }
+
+ Util::forwardEvents($this->readable, $this, array('data', 'end', 'error'));
+ Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe'));
+
+ $this->readable->on('close', array($this, 'close'));
+ $this->writable->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->readable->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->readable->pause();
+ }
+
+ public function resume()
+ {
+ if (!$this->writable->isWritable()) {
+ return;
+ }
+
+ $this->readable->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isWritable()
+ {
+ return $this->writable->isWritable();
+ }
+
+ public function write($data)
+ {
+ return $this->writable->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->readable->pause();
+ $this->writable->end($data);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->readable->close();
+ $this->writable->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/DuplexResourceStream.php b/assets/php/vendor/react/stream/src/DuplexResourceStream.php
new file mode 100644
index 0000000..982ebb0
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/DuplexResourceStream.php
@@ -0,0 +1,224 @@
+isLegacyPipe($stream)) {
+ stream_set_read_buffer($stream, 0);
+ }
+
+ if ($buffer === null) {
+ $buffer = new WritableResourceStream($stream, $loop);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+ $this->buffer = $buffer;
+
+ $that = $this;
+
+ $this->buffer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+
+ $this->buffer->on('close', array($this, 'close'));
+
+ $this->buffer->on('drain', function () use ($that) {
+ $that->emit('drain');
+ });
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && $this->readable) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ return $this->buffer->write($data);
+ }
+
+ public function close()
+ {
+ if (!$this->writable && !$this->closing) {
+ return;
+ }
+
+ $this->closing = false;
+
+ $this->readable = false;
+ $this->writable = false;
+
+ $this->emit('close');
+ $this->pause();
+ $this->buffer->close();
+ $this->removeAllListeners();
+
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ $this->closing = true;
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->pause();
+
+ $this->buffer->end($data);
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ /** @internal */
+ public function handleData($stream)
+ {
+ $error = null;
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = stream_get_contents($stream, $this->bufferSize);
+
+ restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } else{
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512)) {
+ $meta = stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/DuplexStreamInterface.php b/assets/php/vendor/react/stream/src/DuplexStreamInterface.php
new file mode 100644
index 0000000..631ce31
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/DuplexStreamInterface.php
@@ -0,0 +1,39 @@
+ Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see ReadableStreamInterface
+ * @see WritableStreamInterface
+ */
+interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
+{
+}
diff --git a/assets/php/vendor/react/stream/src/ReadableResourceStream.php b/assets/php/vendor/react/stream/src/ReadableResourceStream.php
new file mode 100644
index 0000000..015a96b
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/ReadableResourceStream.php
@@ -0,0 +1,177 @@
+isLegacyPipe($stream)) {
+ stream_set_read_buffer($stream, 0);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && !$this->closed) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+
+ $this->emit('close');
+ $this->pause();
+ $this->removeAllListeners();
+
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleData()
+ {
+ $error = null;
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = stream_get_contents($this->stream, $this->bufferSize);
+
+ restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } else{
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512)) {
+ $meta = stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/ReadableStreamInterface.php b/assets/php/vendor/react/stream/src/ReadableStreamInterface.php
new file mode 100644
index 0000000..2b4c3d0
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/ReadableStreamInterface.php
@@ -0,0 +1,362 @@
+on('data', function ($data) {
+ * echo $data;
+ * });
+ * ```
+ *
+ * This event MAY be emitted any number of times, which may be zero times if
+ * this stream does not send any data at all.
+ * It SHOULD not be emitted after an `end` or `close` event.
+ *
+ * The given `$data` argument may be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit the raw (binary) payload data that is received over the wire as
+ * chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * end event:
+ * The `end` event will be emitted once the source stream has successfully
+ * reached the end of the stream (EOF).
+ *
+ * ```php
+ * $stream->on('end', function () {
+ * echo 'END';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * a successful end was detected.
+ * It SHOULD NOT be emitted after a previous `end` or `close` event.
+ * It MUST NOT be emitted if the stream closes due to a non-successful
+ * end, such as after a previous `error` event.
+ *
+ * After the stream is ended, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * This event will only be emitted if the *end* was reached successfully,
+ * not if the stream was interrupted by an unrecoverable error or explicitly
+ * closed. Not all streams know this concept of a "successful end".
+ * Many use-cases involve detecting when the stream closes (terminates)
+ * instead, in this case you should use the `close` event.
+ * After the stream emits an `end` event, it SHOULD usually be followed by a
+ * `close` event.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit this event if either the remote side closes the connection or
+ * a file handle was successfully read until reaching its end (EOF).
+ *
+ * Note that this event should not be confused with the `end()` method.
+ * This event defines a successful end *reading* from a source stream, while
+ * the `end()` method defines *writing* a successful end to a destination
+ * stream.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to read from this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error or after an unexpected `data` or premature
+ * `end` event.
+ * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-readable mode, see
+ * also `close()` and `isReadable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and do not make assumption about data
+ * boundaries (such as unexpected `data` or premature `end` events).
+ * In other words, many lower-level protocols (such as TCP/IP) may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * Unlike the `end` event, this event SHOULD be emitted whenever the stream
+ * closes, irrespective of whether this happens implicitly due to an
+ * unrecoverable error or explicitly when either side closes the stream.
+ * If you only want to detect a *successful* end, you should use the `end`
+ * event instead.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after reading a *successful* `end`
+ * event or after a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ */
+interface ReadableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a readable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts incoming
+ * data events or if it is ended or closed already.
+ * Once the stream is non-readable, no further `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * A successfully opened stream always MUST start in readable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-readable mode.
+ * This can happen any time, explicitly through `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-readable mode, it MUST NOT transition
+ * back to readable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `isWritable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Pauses reading incoming data events.
+ *
+ * Removes the data source file descriptor from the event loop. This
+ * allows you to throttle incoming data.
+ *
+ * Unless otherwise noted, a successfully opened stream SHOULD NOT start
+ * in paused state.
+ *
+ * Once the stream is paused, no futher `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $stream->on('data', assertShouldNeverCalled());
+ * $stream->on('end', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * stream MAY continue emitting `data` events.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes reading incoming data events.
+ *
+ * Re-attach the data source after a previous `pause()`.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Pipes all the data from this readable source into the given writable destination.
+ *
+ * Automatically sends all incoming data to the destination.
+ * Automatically throttles the source based on what the destination can handle.
+ *
+ * ```php
+ * $source->pipe($dest);
+ * ```
+ *
+ * Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+ * into itself in order to write back all the data that is received.
+ * This may be a useful feature for a TCP/IP echo service:
+ *
+ * ```php
+ * $connection->pipe($connection);
+ * ```
+ *
+ * This method returns the destination stream as-is, which can be used to
+ * set up chains of piped streams:
+ *
+ * ```php
+ * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+ * ```
+ *
+ * By default, this will call `end()` on the destination stream once the
+ * source stream emits an `end` event. This can be disabled like this:
+ *
+ * ```php
+ * $source->pipe($dest, array('end' => false));
+ * ```
+ *
+ * Note that this only applies to the `end` event.
+ * If an `error` or explicit `close` event happens on the source stream,
+ * you'll have to manually close the destination stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $source->on('close', function () use ($dest) {
+ * $dest->end('BYE!');
+ * });
+ * ```
+ *
+ * If the source stream is not readable (closed state), then this is a NO-OP.
+ *
+ * ```php
+ * $source->close();
+ * $source->pipe($dest); // NO-OP
+ * ```
+ *
+ * If the destinantion stream is not writable (closed state), then this will simply
+ * throttle (pause) the source stream:
+ *
+ * ```php
+ * $dest->close();
+ * $source->pipe($dest); // calls $source->pause()
+ * ```
+ *
+ * Similarly, if the destination stream is closed while the pipe is still
+ * active, it will also throttle (pause) the source stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $dest->close(); // calls $source->pause()
+ * ```
+ *
+ * Once the pipe is set up successfully, the destination stream MUST emit
+ * a `pipe` event with this source stream an event argument.
+ *
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ */
+ public function pipe(WritableStreamInterface $dest, array $options = array());
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to (forcefully) close the stream.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-readable
+ * mode, see also `isReadable()`.
+ * This means that no further `data` or `end` events SHOULD be emitted.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this method should not be confused with the `end()` method.
+ *
+ * @return void
+ * @see WritableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/assets/php/vendor/react/stream/src/ThroughStream.php b/assets/php/vendor/react/stream/src/ThroughStream.php
new file mode 100644
index 0000000..da2fbb0
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/ThroughStream.php
@@ -0,0 +1,190 @@
+on('data', $this->expectCallableOnceWith('hello'));
+ *
+ * $through->write('hello');
+ * ```
+ *
+ * Similarly, the [`end()` method](#end) will end the stream and emit an
+ * [`end` event](#end-event) and then [`close()`](#close-1) the stream.
+ * The [`close()` method](#close-1) will close the stream and emit a
+ * [`close` event](#close-event).
+ * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Optionally, its constructor accepts any callable function which will then be
+ * used to *filter* any data written to it. This function receives a single data
+ * argument as passed to the writable side and must return the data as it will be
+ * passed to its readable end:
+ *
+ * ```php
+ * $through = new ThroughStream('strtoupper');
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Note that this class makes no assumptions about any data types. This can be
+ * used to convert data, for example for transforming any structured data into
+ * a newline-delimited JSON (NDJSON) stream like this:
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * return json_encode($data) . PHP_EOL;
+ * });
+ * $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+ *
+ * $through->write(array(2, true));
+ * ```
+ *
+ * The callback function is allowed to throw an `Exception`. In this case,
+ * the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * if (!is_string($data)) {
+ * throw new \UnexpectedValueException('Only strings allowed');
+ * }
+ * return $data;
+ * });
+ * $through->on('error', $this->expectCallableOnce()));
+ * $through->on('close', $this->expectCallableOnce()));
+ * $through->on('data', $this->expectCallableNever()));
+ *
+ * $through->write(2);
+ * ```
+ *
+ * @see WritableStreamInterface::write()
+ * @see WritableStreamInterface::end()
+ * @see DuplexStreamInterface::close()
+ * @see WritableStreamInterface::pipe()
+ */
+final class ThroughStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable = true;
+ private $writable = true;
+ private $closed = false;
+ private $paused = false;
+ private $drain = false;
+ private $callback;
+
+ public function __construct($callback = null)
+ {
+ if ($callback !== null && !is_callable($callback)) {
+ throw new InvalidArgumentException('Invalid transformation callback given');
+ }
+
+ $this->callback = $callback;
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ if ($this->drain) {
+ $this->drain = false;
+ $this->emit('drain');
+ }
+ $this->paused = false;
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ if ($this->callback !== null) {
+ try {
+ $data = call_user_func($this->callback, $data);
+ } catch (\Exception $e) {
+ $this->emit('error', array($e));
+ $this->close();
+
+ return false;
+ }
+ }
+
+ $this->emit('data', array($data));
+
+ if ($this->paused) {
+ $this->drain = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+
+ // return if write() already caused the stream to close
+ if (!$this->writable) {
+ return;
+ }
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->paused = true;
+ $this->drain = false;
+
+ $this->emit('end');
+ $this->close();
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->closed = true;
+ $this->paused = true;
+ $this->drain = false;
+ $this->callback = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/Util.php b/assets/php/vendor/react/stream/src/Util.php
new file mode 100644
index 0000000..14ddcfc
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/Util.php
@@ -0,0 +1,75 @@
+ NO-OP
+ if (!$source->isReadable()) {
+ return $dest;
+ }
+
+ // destination not writable => just pause() source
+ if (!$dest->isWritable()) {
+ $source->pause();
+
+ return $dest;
+ }
+
+ $dest->emit('pipe', array($source));
+
+ // forward all source data events as $dest->write()
+ $source->on('data', $dataer = function ($data) use ($source, $dest) {
+ $feedMore = $dest->write($data);
+
+ if (false === $feedMore) {
+ $source->pause();
+ }
+ });
+ $dest->on('close', function () use ($source, $dataer) {
+ $source->removeListener('data', $dataer);
+ $source->pause();
+ });
+
+ // forward destination drain as $source->resume()
+ $dest->on('drain', $drainer = function () use ($source) {
+ $source->resume();
+ });
+ $source->on('close', function () use ($dest, $drainer) {
+ $dest->removeListener('drain', $drainer);
+ });
+
+ // forward end event from source as $dest->end()
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end) {
+ $source->on('end', $ender = function () use ($dest) {
+ $dest->end();
+ });
+ $dest->on('close', function () use ($source, $ender) {
+ $source->removeListener('end', $ender);
+ });
+ }
+
+ return $dest;
+ }
+
+ public static function forwardEvents($source, $target, array $events)
+ {
+ foreach ($events as $event) {
+ $source->on($event, function () use ($event, $target) {
+ $target->emit($event, func_get_args());
+ });
+ }
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/WritableResourceStream.php b/assets/php/vendor/react/stream/src/WritableResourceStream.php
new file mode 100644
index 0000000..7e04205
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/WritableResourceStream.php
@@ -0,0 +1,171 @@
+stream = $stream;
+ $this->loop = $loop;
+ $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit;
+ $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ $this->data .= $data;
+
+ if (!$this->listening && $this->data !== '') {
+ $this->listening = true;
+
+ $this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
+ }
+
+ return !isset($this->data[$this->softLimit - 1]);
+ }
+
+ public function end($data = null)
+ {
+ if (null !== $data) {
+ $this->write($data);
+ }
+
+ $this->writable = false;
+
+ // close immediately if buffer is already empty
+ // otherwise wait for buffer to flush first
+ if ($this->data === '') {
+ $this->close();
+ }
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ if ($this->listening) {
+ $this->listening = false;
+ $this->loop->removeWriteStream($this->stream);
+ }
+
+ $this->closed = true;
+ $this->writable = false;
+ $this->data = '';
+
+ $this->emit('close');
+ $this->removeAllListeners();
+
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleWrite()
+ {
+ $error = null;
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = array(
+ 'message' => $errstr,
+ 'number' => $errno,
+ 'file' => $errfile,
+ 'line' => $errline
+ );
+ });
+
+ if ($this->writeChunkSize === -1) {
+ $sent = fwrite($this->stream, $this->data);
+ } else {
+ $sent = fwrite($this->stream, $this->data, $this->writeChunkSize);
+ }
+
+ restore_error_handler();
+
+ // Only report errors if *nothing* could be sent.
+ // Any hard (permanent) error will fail to send any data at all.
+ // Sending excessive amounts of data will only flush *some* data and then
+ // report a temporary error (EAGAIN) which we do not raise here in order
+ // to keep the stream open for further tries to write.
+ // Should this turn out to be a permanent error later, it will eventually
+ // send *nothing* and we can detect this.
+ if ($sent === 0 || $sent === false) {
+ if ($error !== null) {
+ $error = new \ErrorException(
+ $error['message'],
+ 0,
+ $error['number'],
+ $error['file'],
+ $error['line']
+ );
+ }
+
+ $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . ($error !== null ? $error->getMessage() : 'Unknown error'), 0, $error)));
+ $this->close();
+
+ return;
+ }
+
+ $exceeded = isset($this->data[$this->softLimit - 1]);
+ $this->data = (string) substr($this->data, $sent);
+
+ // buffer has been above limit and is now below limit
+ if ($exceeded && !isset($this->data[$this->softLimit - 1])) {
+ $this->emit('drain');
+ }
+
+ // buffer is now completely empty => stop trying to write
+ if ($this->data === '') {
+ // stop waiting for resource to be writable
+ if ($this->listening) {
+ $this->loop->removeWriteStream($this->stream);
+ $this->listening = false;
+ }
+
+ // buffer is end()ing and now completely empty => close buffer
+ if (!$this->writable) {
+ $this->close();
+ }
+ }
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/WritableStreamInterface.php b/assets/php/vendor/react/stream/src/WritableStreamInterface.php
new file mode 100644
index 0000000..3bc932e
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/WritableStreamInterface.php
@@ -0,0 +1,347 @@
+on('drain', function () use ($stream) {
+ * echo 'Stream is now ready to accept more data';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once every time the buffer became full
+ * previously and is now ready to accept more data.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if the buffer never became full in the first place.
+ * This event SHOULD NOT be emitted if the buffer has not become full
+ * previously.
+ *
+ * This event is mostly used internally, see also `write()` for more details.
+ *
+ * pipe event:
+ * The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+ * into this stream.
+ * The event receives a single `ReadableStreamInterface` argument for the
+ * source stream.
+ *
+ * ```php
+ * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ * echo 'Now receiving piped data';
+ *
+ * // explicitly close target if source emits an error
+ * $source->on('error', function () use ($stream) {
+ * $stream->close();
+ * });
+ * });
+ *
+ * $source->pipe($stream);
+ * ```
+ *
+ * This event MUST be emitted once for each readable stream that is
+ * successfully piped into this destination stream.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if no stream is ever piped into this stream.
+ * This event MUST NOT be emitted if either the source is not readable
+ * (closed already) or this destination is not writable (closed already).
+ *
+ * This event is mostly used internally, see also `pipe()` for more details.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to write to this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error.
+ * It SHOULD NOT be emitted after a previous `error` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-writable mode, see
+ * also `close()` and `isWritable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-writable mode,
+ * see also `isWritable()`.
+ *
+ * This event SHOULD be emitted whenever the stream closes, irrespective of
+ * whether this happens implicitly due to an unrecoverable error or
+ * explicitly when either side closes the stream.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after flushing the buffer from
+ * the `end()` method, after receiving a *successful* `end` event or after
+ * a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ * @see DuplexStreamInterface
+ */
+interface WritableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a writable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts writing
+ * any data or if it is ended or closed already.
+ * Writing any data to a non-writable stream is a NO-OP:
+ *
+ * ```php
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('end'); // NO-OP
+ * $stream->end('end'); // NO-OP
+ * ```
+ *
+ * A successfully opened stream always MUST start in writable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-writable mode.
+ * This can happen any time, explicitly through `end()` or `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-writable mode, it MUST NOT transition
+ * back to writable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `isReadable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write some data into the stream.
+ *
+ * A successful write MUST be confirmed with a boolean `true`, which means
+ * that either the data was written (flushed) immediately or is buffered and
+ * scheduled for a future write. Note that this interface gives you no
+ * control over explicitly flushing the buffered data, as finding the
+ * appropriate time for this is beyond the scope of this interface and left
+ * up to the implementation of this interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+ * an `error` event and MAY `close()` the stream if it can not recover from
+ * this error.
+ *
+ * If the internal buffer is full after adding `$data`, then `write()`
+ * SHOULD return `false`, indicating that the caller should stop sending
+ * data until the buffer drains.
+ * The stream SHOULD send a `drain` event once the buffer is ready to accept
+ * more data.
+ *
+ * Similarly, if the the stream is not writable (already in a closed state)
+ * it MUST NOT process the given `$data` and SHOULD return `false`,
+ * indicating that the caller should stop sending data.
+ *
+ * The given `$data` argument MAY be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will only accept the raw (binary) payload data that is transferred over
+ * the wire as chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * @param mixed|string $data
+ * @return bool
+ */
+ public function write($data);
+
+ /**
+ * Successfully ends the stream (after optionally sending some final data).
+ *
+ * This method can be used to successfully end the stream, i.e. close
+ * the stream after sending out all data that is currently buffered.
+ *
+ * ```php
+ * $stream->write('hello');
+ * $stream->write('world');
+ * $stream->end();
+ * ```
+ *
+ * If there's no data currently buffered and nothing to be flushed, then
+ * this method MAY `close()` the stream immediately.
+ *
+ * If there's still data in the buffer that needs to be flushed first, then
+ * this method SHOULD try to write out this data and only then `close()`
+ * the stream.
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ *
+ * Note that this interface gives you no control over explicitly flushing
+ * the buffered data, as finding the appropriate time for this is beyond the
+ * scope of this interface and left up to the implementation of this
+ * interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * You can optionally pass some final data that is written to the stream
+ * before ending the stream. If a non-`null` value is given as `$data`, then
+ * this method will behave just like calling `write($data)` before ending
+ * with no data.
+ *
+ * ```php
+ * // shorter version
+ * $stream->end('bye');
+ *
+ * // same as longer version
+ * $stream->write('bye');
+ * $stream->end();
+ * ```
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->end();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+ * also end its readable side, unless the stream supports half-open mode.
+ * In other words, after calling this method, these streams SHOULD switch
+ * into non-writable AND non-readable mode, see also `isReadable()`.
+ * This implies that in this case, the stream SHOULD NOT emit any `data`
+ * or `end` events anymore.
+ * Streams MAY choose to use the `pause()` method logic for this, but
+ * special care may have to be taken to ensure a following call to the
+ * `resume()` method SHOULD NOT continue emitting readable events.
+ *
+ * Note that this method should not be confused with the `close()` method.
+ *
+ * @param mixed|string|null $data
+ * @return void
+ */
+ public function end($data = null);
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to forcefully close the stream, i.e. close
+ * the stream without waiting for any buffered data to be flushed.
+ * If there's still data in the buffer, this data SHOULD be discarded.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * Note that this method should not be confused with the `end()` method.
+ * Unlike the `end()` method, this method does not take care of any existing
+ * buffers and simply discards any buffer contents.
+ * Likewise, this method may also be called after calling `end()` on a
+ * stream in order to stop waiting for the stream to flush its final data.
+ *
+ * ```php
+ * $stream->end();
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ *
+ * @return void
+ * @see ReadableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/assets/php/vendor/react/stream/tests/CallableStub.php b/assets/php/vendor/react/stream/tests/CallableStub.php
new file mode 100644
index 0000000..31cc834
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/CallableStub.php
@@ -0,0 +1,10 @@
+getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(false);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldCloseWritableIfNotReadable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardWritableCallsToWritableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->write('foo');
+ $composite->isWritable();
+ }
+
+ /** @test */
+ public function itShouldForwardReadableCallsToReadableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->exactly(2))
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+ $readable
+ ->expects($this->once())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->isReadable();
+ $composite->pause();
+ $composite->resume();
+ }
+
+ /** @test */
+ public function itShouldNotForwardResumeIfStreamIsNotWritable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->never())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturnOnConsecutiveCalls(true, false);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->resume();
+ }
+
+ /** @test */
+ public function endShouldDelegateToWritableWithData()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->end('foo');
+ }
+
+ /** @test */
+ public function closeShouldCloseBothStreams()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseOnlyOnce()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $readable->close();
+ $writable->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseAndRemoveAllListeners()
+ {
+ $in = new ThroughStream();
+
+ $composite = new CompositeStream($in, $in);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($composite->isReadable());
+ $this->assertTrue($composite->isWritable());
+ $this->assertCount(1, $composite->listeners('close'));
+
+ $composite->close();
+
+ $this->assertFalse($composite->isReadable());
+ $this->assertFalse($composite->isWritable());
+ $this->assertCount(0, $composite->listeners('close'));
+ }
+
+ /** @test */
+ public function itShouldReceiveForwardedEvents()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('data', $this->expectCallableOnce());
+ $composite->on('drain', $this->expectCallableOnce());
+
+ $readable->emit('data', array('foo'));
+ $writable->emit('drain');
+ }
+
+ /** @test */
+ public function itShouldHandlePipingCorrectly()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $input = new ThroughStream();
+ $input->pipe($composite);
+ $input->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function itShouldForwardPipeCallsToReadableStream()
+ {
+ $readable = new ThroughStream();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite->pipe($output);
+ $readable->emit('data', array('foo'));
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php b/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
new file mode 100644
index 0000000..fb5f02a
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
@@ -0,0 +1,352 @@
+markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $bufferSize = 4096;
+ $streamA = new DuplexResourceStream($sockA, $loop, $bufferSize);
+ $streamB = new DuplexResourceStream($sockB, $loop, $bufferSize);
+
+ $testString = str_repeat("*", $bufferSize + 1);
+
+ $buffer = "";
+ $streamB->on('data', function ($data) use (&$buffer) {
+ $buffer .= $data;
+ });
+
+ $streamA->write($testString);
+
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($testString, $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testWriteLargeChunk($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // limit seems to be 192 KiB
+ $size = 256 * 1024;
+
+ // sending side sends and expects clean close with no errors
+ $streamA->end(str_repeat('*', $size));
+ $streamA->on('close', $this->expectCallableOnce());
+ $streamA->on('error', $this->expectCallableNever());
+
+ // receiving side counts bytes and expects clean close with no errors
+ $received = 0;
+ $streamB->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ $streamB->on('close', $this->expectCallableOnce());
+ $streamB->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($size, $received);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->end();
+
+ // streamB should not emit any data
+ $streamB->on('data', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($client, $loop);
+ $streamB = new DuplexResourceStream($peer, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($peer, $loop);
+ $streamB = new DuplexResourceStream($client, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo test', 'r'), $loop);
+ $stream->on('data', $this->expectCallableOnceWith("test\n"));
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals("a\n" . "b\n" . "c\n", $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop);
+
+ $bytes = 0;
+ $stream->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals(12345 * 1234, $bytes);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('true', 'r'), $loop);
+ $stream->on('data', $this->expectCallableNever());
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ private function loopTick(LoopInterface $loop)
+ {
+ $loop->addTimer(0, function () use ($loop) {
+ $loop->stop();
+ });
+ $loop->run();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php b/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php
new file mode 100644
index 0000000..3212ae8
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php
@@ -0,0 +1,495 @@
+createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new DuplexResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream('breakme', $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException RunTimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorAcceptsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ }
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+ $conn->on('end', $this->expectCallableNever());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testEndShouldEndBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->once())->method('end')->with('foo');
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ $conn->end('foo');
+ }
+
+
+ public function testEndAfterCloseIsNoOp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->never())->method('end');
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->end();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::write
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+
+ rewind($stream);
+ $this->assertSame("foo\n", fgets($stream));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ * @covers React\Stream\DuplexResourceStream::isReadable
+ * @covers React\Stream\DuplexResourceStream::isWritable
+ */
+ public function testEnd()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end();
+
+ $this->assertFalse(is_resource($stream));
+ $this->assertFalse($conn->isReadable());
+ $this->assertFalse($conn->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ */
+ public function testEndRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end('bye');
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ public function testEndedStreamsShouldNotWrite()
+ {
+ $file = tempnam(sys_get_temp_dir(), 'reactphptest_');
+ $stream = fopen($file, 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+ $conn->end();
+
+ $res = $conn->write("bar\n");
+ $stream = fopen($file, 'r');
+
+ $this->assertSame("foo\n", fgets($stream));
+ $this->assertFalse($res);
+
+ unlink($file);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ public function testBufferEventsShouldBubbleUp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+
+ $conn->on('drain', $this->expectCallableOnce());
+ $conn->on('error', $this->expectCallableOnce());
+
+ $buffer->emit('drain');
+ $buffer->emit('error', array(new \RuntimeException('Whoops')));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop
+ ->expects($this->once())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) {
+ call_user_func($listener, $stream);
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php b/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php
new file mode 100644
index 0000000..39c0487
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php
@@ -0,0 +1,35 @@
+on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockPlain()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tcp://httpbin.org:80');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadKilobyteSecure()
+ {
+ $size = 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockSecureRequiresSmallerChunkSize()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream(
+ $stream,
+ $loop,
+ null,
+ new WritableResourceStream($stream, $loop, null, 8192)
+ );
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0)
+ {
+ $stream->on('close', function () use ($loop) {
+ $loop->stop();
+ });
+
+ $that = $this;
+ $loop->addTimer($timeout, function () use ($loop, $that) {
+ $loop->stop();
+ $that->fail('Timed out while waiting for stream to close');
+ });
+
+ $loop->run();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php b/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php
new file mode 100644
index 0000000..20da96f
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php
@@ -0,0 +1,372 @@
+createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new ReadableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(false, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testCloseTwiceShouldEmitCloseEventOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php b/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php
new file mode 100644
index 0000000..6984f24
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php
@@ -0,0 +1,61 @@
+emit('data', array($data));
+ }
+
+ // trigger error event
+ public function error($error)
+ {
+ $this->emit('error', array($error));
+ }
+
+ // trigger end event
+ public function end()
+ {
+ $this->emit('end', array());
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ $this->paused = false;
+ }
+
+ public function close()
+ {
+ $this->readable = false;
+
+ $this->emit('close');
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/TestCase.php b/assets/php/vendor/react/stream/tests/TestCase.php
new file mode 100644
index 0000000..c8fc1db
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/TestCase.php
@@ -0,0 +1,54 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $callback;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/ThroughStreamTest.php b/assets/php/vendor/react/stream/tests/ThroughStreamTest.php
new file mode 100644
index 0000000..a98badf
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/ThroughStreamTest.php
@@ -0,0 +1,267 @@
+write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToIt()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruFunction()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruCallback()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsException()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->write('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $ret = $through->write('foo');
+
+ $this->assertFalse($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->write('foo');
+
+ $through->on('drain', $this->expectCallableOnce());
+ $through->resume();
+ }
+
+ /** @test */
+ public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause()
+ {
+ $through = new ThroughStream();
+ $through->on('drain', $this->expectCallableNever());
+ $through->pause();
+ $through->resume();
+ $ret = $through->write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function pipingStuffIntoItShouldWork()
+ {
+ $readable = new ThroughStream();
+
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+
+ $readable->pipe($through);
+ $readable->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function endShouldEmitEndAndClose()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->end();
+ }
+
+ /** @test */
+ public function endShouldCloseTheStream()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endShouldWriteDataBeforeClosing()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endTwiceShouldOnlyEmitOnce()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnce('first'));
+ $through->end('first');
+ $through->end('ignored');
+ }
+
+ /** @test */
+ public function writeAfterEndShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataWillCloseStreamShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', array($through, 'close'));
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToPausedShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToResumedShouldReturnTrue()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->resume();
+
+ $this->assertTrue($through->write('foo'));
+ }
+
+ /** @test */
+ public function itShouldBeReadableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isReadable());
+ }
+
+ /** @test */
+ public function itShouldBeWritableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isWritable());
+ }
+
+ /** @test */
+ public function closeShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function doubleCloseShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function pipeShouldPipeCorrectly()
+ {
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $through = new ThroughStream();
+ $through->pipe($output);
+ $through->write('foo');
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/UtilTest.php b/assets/php/vendor/react/stream/tests/UtilTest.php
new file mode 100644
index 0000000..3d113ab
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/UtilTest.php
@@ -0,0 +1,273 @@
+getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $ret = Util::pipe($readable, $writable);
+
+ $this->assertSame($writable, $ret);
+ }
+
+ public function testPipeNonReadableSourceShouldDoNothing()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->never())
+ ->method('isWritable');
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeIntoNonWritableDestinationShouldPauseSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(false);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeClosingDestPausesSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = new ThroughStream();
+
+ Util::pipe($readable, $writable);
+
+ $writable->close();
+ }
+
+ public function testPipeWithEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+
+ $readable->end();
+ }
+
+ public function testPipeWithoutEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable, array('end' => false));
+
+ $readable->end();
+ }
+
+ public function testPipeWithTooSlowWritableShouldPauseReadable()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('some data')
+ ->will($this->returnValue(false));
+
+ $readable->pipe($writable);
+
+ $this->assertFalse($readable->paused);
+ $readable->write('some data');
+ $this->assertTrue($readable->paused);
+ }
+
+ public function testPipeWithTooSlowWritableShouldResumeOnDrain()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $onDrain = null;
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->any())
+ ->method('on')
+ ->will($this->returnCallback(function ($name, $callback) use (&$onDrain) {
+ if ($name === 'drain') {
+ $onDrain = $callback;
+ }
+ }));
+
+ $readable->pipe($writable);
+ $readable->pause();
+
+ $this->assertTrue($readable->paused);
+ $this->assertNotNull($onDrain);
+ $onDrain();
+ $this->assertFalse($readable->paused);
+ }
+
+ public function testPipeWithWritableResourceStream()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $readable->pipe($buffer);
+
+ $readable->write('hello, I am some ');
+ $readable->write('random data');
+
+ $buffer->handleWrite();
+ rewind($stream);
+ $this->assertSame('hello, I am some random data', stream_get_contents($stream));
+ }
+
+ public function testPipeSetsUpListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+
+ Util::pipe($source, $dest);
+
+ $this->assertCount(1, $source->listeners('data'));
+ $this->assertCount(1, $source->listeners('end'));
+ $this->assertCount(1, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingSourceRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $source->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingDestRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $dest->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeDuplexIntoSelfEndsOnEnd()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable->expects($this->any())->method('isReadable')->willReturn(true);
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(true);
+ $duplex = new CompositeStream($readable, $writable);
+
+ Util::pipe($duplex, $duplex);
+
+ $writable->expects($this->once())->method('end');
+
+ $duplex->emit('end');
+ }
+
+ /** @test */
+ public function forwardEventsShouldSetupForwards()
+ {
+ $source = new ThroughStream();
+ $target = new ThroughStream();
+
+ Util::forwardEvents($source, $target, array('data'));
+ $target->on('data', $this->expectCallableOnce());
+ $target->on('foo', $this->expectCallableNever());
+
+ $source->emit('data', array('hello'));
+ $source->emit('foo', array('bar'));
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+
+ private function notEqualTo($value)
+ {
+ return new \PHPUnit_Framework_Constraint_Not($value);
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php b/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php
new file mode 100644
index 0000000..05bce9c
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php
@@ -0,0 +1,534 @@
+createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'w+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsIfNotAValidStreamResource()
+ {
+ $stream = null;
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStream()
+ {
+ $stream = fopen('php://temp', 'r');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'reANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->write("foobar\n");
+ rewind($stream);
+ $this->assertSame("foobar\n", fread($stream, 1024));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteWithDataDoesAddResourceToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream));
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("foobar\n");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmptyWriteDoesNotAddToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->never())->method('addWriteStream');
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("");
+ $buffer->write(null);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+ $loop->preventWrites = true;
+
+ $buffer = new WritableResourceStream($stream, $loop, 4);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $this->assertTrue($buffer->write("foo"));
+ $loop->preventWrites = false;
+ $this->assertFalse($buffer->write("bar\n"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 3);
+
+ $this->assertFalse($buffer->write("foo"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteDetectsWhenOtherSideIsClosed()
+ {
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($a, $loop, 4);
+ $buffer->on('error', $this->expectCallableOnce());
+
+ fclose($b);
+
+ $buffer->write("foo");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmitsDrainAfterWriteWhichExceedsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteInDrain()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->once('drain', function () use ($buffer) {
+ $buffer->write("bar\n");
+ $buffer->handleWrite();
+ });
+
+ $this->assertFalse($buffer->write("foo\n"));
+ $buffer->handleWrite();
+
+ fseek($stream, 0);
+ $this->assertSame("foo\nbar\n", stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', function () use ($buffer) {
+ $buffer->close();
+ });
+
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ $buffer->handleWrite();
+ $this->assertSame('final words', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ rewind($stream);
+ $this->assertSame('', stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::isWritable
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->close();
+ $this->assertFalse($buffer->isWritable());
+
+ $this->assertEquals(array(), $buffer->listeners('close'));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingAfterWriteRemovesStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer->write('foo');
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->never())->method('removeWriteStream');
+
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testDoubleCloseWillEmitOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->close();
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $buffer->close();
+
+ $buffer->write('foo');
+
+ $buffer->handleWrite();
+ $this->assertSame('', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testErrorWhenStreamResourceIsInvalid()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', function ($message) use (&$error) {
+ $error = $message;
+ });
+
+ // invalidate stream resource
+ fclose($stream);
+
+ $buffer->write('Attempting to write to bad stream');
+
+ $this->assertInstanceOf('Exception', $error);
+
+ // the error messages differ between PHP versions, let's just check substrings
+ $this->assertContains('Unable to write to stream: ', $error->getMessage());
+ $this->assertContains(' not a valid stream resource', $error->getMessage(), '', true);
+ }
+
+ public function testWritingToClosedStream()
+ {
+ if ('Darwin' === PHP_OS) {
+ $this->markTestSkipped('OS X issue with shutting down pair for writing');
+ }
+
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+ $loop = $this->createLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($a, $loop);
+ $buffer->on('error', function($message) use (&$error) {
+ $error = $message;
+ });
+
+ $buffer->write('foo');
+ $buffer->handleWrite();
+ stream_socket_shutdown($b, STREAM_SHUT_RD);
+ stream_socket_shutdown($a, STREAM_SHUT_RD);
+ $buffer->write('bar');
+ $buffer->handleWrite();
+
+ $this->assertInstanceOf('Exception', $error);
+ $this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop->preventWrites = false;
+ $loop
+ ->expects($this->any())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) use ($loop) {
+ if (!$loop->preventWrites) {
+ call_user_func($listener, $stream);
+ }
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/.gitignore b/assets/php/vendor/symfony/http-foundation/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/assets/php/vendor/symfony/http-foundation/AcceptHeader.php b/assets/php/vendor/symfony/http-foundation/AcceptHeader.php
new file mode 100644
index 0000000..d174026
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/AcceptHeader.php
@@ -0,0 +1,168 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents an Accept-* header.
+ *
+ * An accept header is compound with a list of items,
+ * sorted by descending quality.
+ *
+ * @author Jean-François Simon
+ */
+class AcceptHeader
+{
+ /**
+ * @var AcceptHeaderItem[]
+ */
+ private $items = array();
+
+ /**
+ * @var bool
+ */
+ private $sorted = true;
+
+ /**
+ * @param AcceptHeaderItem[] $items
+ */
+ public function __construct(array $items)
+ {
+ foreach ($items as $item) {
+ $this->add($item);
+ }
+ }
+
+ /**
+ * Builds an AcceptHeader instance from a string.
+ *
+ * @param string $headerValue
+ *
+ * @return self
+ */
+ public static function fromString($headerValue)
+ {
+ $index = 0;
+
+ return new self(array_map(function ($itemValue) use (&$index) {
+ $item = AcceptHeaderItem::fromString($itemValue);
+ $item->setIndex($index++);
+
+ return $item;
+ }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)));
+ }
+
+ /**
+ * Returns header value's string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return implode(',', $this->items);
+ }
+
+ /**
+ * Tests if header has given value.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ public function has($value)
+ {
+ return isset($this->items[$value]);
+ }
+
+ /**
+ * Returns given value's item, if exists.
+ *
+ * @param string $value
+ *
+ * @return AcceptHeaderItem|null
+ */
+ public function get($value)
+ {
+ return isset($this->items[$value]) ? $this->items[$value] : null;
+ }
+
+ /**
+ * Adds an item.
+ *
+ * @return $this
+ */
+ public function add(AcceptHeaderItem $item)
+ {
+ $this->items[$item->getValue()] = $item;
+ $this->sorted = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns all items.
+ *
+ * @return AcceptHeaderItem[]
+ */
+ public function all()
+ {
+ $this->sort();
+
+ return $this->items;
+ }
+
+ /**
+ * Filters items on their value using given regex.
+ *
+ * @param string $pattern
+ *
+ * @return self
+ */
+ public function filter($pattern)
+ {
+ return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
+ return preg_match($pattern, $item->getValue());
+ }));
+ }
+
+ /**
+ * Returns first item.
+ *
+ * @return AcceptHeaderItem|null
+ */
+ public function first()
+ {
+ $this->sort();
+
+ return !empty($this->items) ? reset($this->items) : null;
+ }
+
+ /**
+ * Sorts items by descending quality.
+ */
+ private function sort()
+ {
+ if (!$this->sorted) {
+ uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) {
+ $qA = $a->getQuality();
+ $qB = $b->getQuality();
+
+ if ($qA === $qB) {
+ return $a->getIndex() > $b->getIndex() ? 1 : -1;
+ }
+
+ return $qA > $qB ? -1 : 1;
+ });
+
+ $this->sorted = true;
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/AcceptHeaderItem.php b/assets/php/vendor/symfony/http-foundation/AcceptHeaderItem.php
new file mode 100644
index 0000000..c69dbbb
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/AcceptHeaderItem.php
@@ -0,0 +1,209 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents an Accept-* header item.
+ *
+ * @author Jean-François Simon
+ */
+class AcceptHeaderItem
+{
+ private $value;
+ private $quality = 1.0;
+ private $index = 0;
+ private $attributes = array();
+
+ /**
+ * @param string $value
+ * @param array $attributes
+ */
+ public function __construct($value, array $attributes = array())
+ {
+ $this->value = $value;
+ foreach ($attributes as $name => $value) {
+ $this->setAttribute($name, $value);
+ }
+ }
+
+ /**
+ * Builds an AcceptHeaderInstance instance from a string.
+ *
+ * @param string $itemValue
+ *
+ * @return self
+ */
+ public static function fromString($itemValue)
+ {
+ $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+ $value = array_shift($bits);
+ $attributes = array();
+
+ $lastNullAttribute = null;
+ foreach ($bits as $bit) {
+ if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ('"' === $start || '\'' === $start)) {
+ $attributes[$lastNullAttribute] = substr($bit, 1, -1);
+ } elseif ('=' === $end) {
+ $lastNullAttribute = $bit = substr($bit, 0, -1);
+ $attributes[$bit] = null;
+ } else {
+ $parts = explode('=', $bit);
+ $attributes[$parts[0]] = isset($parts[1]) && strlen($parts[1]) > 0 ? $parts[1] : '';
+ }
+ }
+
+ return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ('"' === $start || '\'' === $start) ? substr($value, 1, -1) : $value, $attributes);
+ }
+
+ /**
+ * Returns header value's string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
+ if (count($this->attributes) > 0) {
+ $string .= ';'.implode(';', array_map(function ($name, $value) {
+ return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value);
+ }, array_keys($this->attributes), $this->attributes));
+ }
+
+ return $string;
+ }
+
+ /**
+ * Set the item value.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item value.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set the item quality.
+ *
+ * @param float $quality
+ *
+ * @return $this
+ */
+ public function setQuality($quality)
+ {
+ $this->quality = $quality;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item quality.
+ *
+ * @return float
+ */
+ public function getQuality()
+ {
+ return $this->quality;
+ }
+
+ /**
+ * Set the item index.
+ *
+ * @param int $index
+ *
+ * @return $this
+ */
+ public function setIndex($index)
+ {
+ $this->index = $index;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item index.
+ *
+ * @return int
+ */
+ public function getIndex()
+ {
+ return $this->index;
+ }
+
+ /**
+ * Tests if an attribute exists.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasAttribute($name)
+ {
+ return isset($this->attributes[$name]);
+ }
+
+ /**
+ * Returns an attribute by its name.
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * Returns all attributes.
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setAttribute($name, $value)
+ {
+ if ('q' === $name) {
+ $this->quality = (float) $value;
+ } else {
+ $this->attributes[$name] = (string) $value;
+ }
+
+ return $this;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/ApacheRequest.php b/assets/php/vendor/symfony/http-foundation/ApacheRequest.php
new file mode 100644
index 0000000..84803eb
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/ApacheRequest.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Request represents an HTTP request from an Apache server.
+ *
+ * @author Fabien Potencier
+ */
+class ApacheRequest extends Request
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function prepareRequestUri()
+ {
+ return $this->server->get('REQUEST_URI');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function prepareBaseUrl()
+ {
+ $baseUrl = $this->server->get('SCRIPT_NAME');
+
+ if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) {
+ // assume mod_rewrite
+ return rtrim(dirname($baseUrl), '/\\');
+ }
+
+ return $baseUrl;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/BinaryFileResponse.php b/assets/php/vendor/symfony/http-foundation/BinaryFileResponse.php
new file mode 100644
index 0000000..1010223
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/BinaryFileResponse.php
@@ -0,0 +1,359 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\File\File;
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+
+/**
+ * BinaryFileResponse represents an HTTP response delivering a file.
+ *
+ * @author Niklas Fiekas
+ * @author stealth35
+ * @author Igor Wiedler
+ * @author Jordan Alliot
+ * @author Sergey Linnik
+ */
+class BinaryFileResponse extends Response
+{
+ protected static $trustXSendfileTypeHeader = false;
+
+ /**
+ * @var File
+ */
+ protected $file;
+ protected $offset;
+ protected $maxlen;
+ protected $deleteFileAfterSend = false;
+
+ /**
+ * @param \SplFileInfo|string $file The file to stream
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ * @param bool $public Files are public by default
+ * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename
+ * @param bool $autoEtag Whether the ETag header should be automatically set
+ * @param bool $autoLastModified Whether the Last-Modified header should be automatically set
+ */
+ public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ parent::__construct(null, $status, $headers);
+
+ $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
+
+ if ($public) {
+ $this->setPublic();
+ }
+ }
+
+ /**
+ * @param \SplFileInfo|string $file The file to stream
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ * @param bool $public Files are public by default
+ * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename
+ * @param bool $autoEtag Whether the ETag header should be automatically set
+ * @param bool $autoLastModified Whether the Last-Modified header should be automatically set
+ *
+ * @return static
+ */
+ public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
+ }
+
+ /**
+ * Sets the file to stream.
+ *
+ * @param \SplFileInfo|string $file The file to stream
+ * @param string $contentDisposition
+ * @param bool $autoEtag
+ * @param bool $autoLastModified
+ *
+ * @return $this
+ *
+ * @throws FileException
+ */
+ public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ if (!$file instanceof File) {
+ if ($file instanceof \SplFileInfo) {
+ $file = new File($file->getPathname());
+ } else {
+ $file = new File((string) $file);
+ }
+ }
+
+ if (!$file->isReadable()) {
+ throw new FileException('File must be readable.');
+ }
+
+ $this->file = $file;
+
+ if ($autoEtag) {
+ $this->setAutoEtag();
+ }
+
+ if ($autoLastModified) {
+ $this->setAutoLastModified();
+ }
+
+ if ($contentDisposition) {
+ $this->setContentDisposition($contentDisposition);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets the file.
+ *
+ * @return File The file to stream
+ */
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ /**
+ * Automatically sets the Last-Modified header according the file modification date.
+ */
+ public function setAutoLastModified()
+ {
+ $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
+
+ return $this;
+ }
+
+ /**
+ * Automatically sets the ETag header according to the checksum of the file.
+ */
+ public function setAutoEtag()
+ {
+ $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true)));
+
+ return $this;
+ }
+
+ /**
+ * Sets the Content-Disposition header with the given filename.
+ *
+ * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
+ * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file
+ * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
+ *
+ * @return $this
+ */
+ public function setContentDisposition($disposition, $filename = '', $filenameFallback = '')
+ {
+ if ('' === $filename) {
+ $filename = $this->file->getFilename();
+ }
+
+ if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) {
+ $encoding = mb_detect_encoding($filename, null, true) ?: '8bit';
+
+ for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
+ $char = mb_substr($filename, $i, 1, $encoding);
+
+ if ('%' === $char || ord($char) < 32 || ord($char) > 126) {
+ $filenameFallback .= '_';
+ } else {
+ $filenameFallback .= $char;
+ }
+ }
+ }
+
+ $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
+ $this->headers->set('Content-Disposition', $dispositionHeader);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prepare(Request $request)
+ {
+ if (!$this->headers->has('Content-Type')) {
+ $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
+ }
+
+ if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) {
+ $this->setProtocolVersion('1.1');
+ }
+
+ $this->ensureIEOverSSLCompatibility($request);
+
+ $this->offset = 0;
+ $this->maxlen = -1;
+
+ if (false === $fileSize = $this->file->getSize()) {
+ return $this;
+ }
+ $this->headers->set('Content-Length', $fileSize);
+
+ if (!$this->headers->has('Accept-Ranges')) {
+ // Only accept ranges on safe HTTP methods
+ $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none');
+ }
+
+ if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
+ // Use X-Sendfile, do not send any content.
+ $type = $request->headers->get('X-Sendfile-Type');
+ $path = $this->file->getRealPath();
+ // Fall back to scheme://path for stream wrapped locations.
+ if (false === $path) {
+ $path = $this->file->getPathname();
+ }
+ if ('x-accel-redirect' === strtolower($type)) {
+ // Do X-Accel-Mapping substitutions.
+ // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect
+ foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) {
+ $mapping = explode('=', $mapping, 2);
+
+ if (2 === count($mapping)) {
+ $pathPrefix = trim($mapping[0]);
+ $location = trim($mapping[1]);
+
+ if (substr($path, 0, strlen($pathPrefix)) === $pathPrefix) {
+ $path = $location.substr($path, strlen($pathPrefix));
+ break;
+ }
+ }
+ }
+ }
+ $this->headers->set($type, $path);
+ $this->maxlen = 0;
+ } elseif ($request->headers->has('Range')) {
+ // Process the range headers.
+ if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
+ $range = $request->headers->get('Range');
+
+ list($start, $end) = explode('-', substr($range, 6), 2) + array(0);
+
+ $end = ('' === $end) ? $fileSize - 1 : (int) $end;
+
+ if ('' === $start) {
+ $start = $fileSize - $end;
+ $end = $fileSize - 1;
+ } else {
+ $start = (int) $start;
+ }
+
+ if ($start <= $end) {
+ if ($start < 0 || $end > $fileSize - 1) {
+ $this->setStatusCode(416);
+ $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
+ } elseif (0 !== $start || $end !== $fileSize - 1) {
+ $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
+ $this->offset = $start;
+
+ $this->setStatusCode(206);
+ $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
+ $this->headers->set('Content-Length', $end - $start + 1);
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ private function hasValidIfRangeHeader($header)
+ {
+ if ($this->getEtag() === $header) {
+ return true;
+ }
+
+ if (null === $lastModified = $this->getLastModified()) {
+ return false;
+ }
+
+ return $lastModified->format('D, d M Y H:i:s').' GMT' === $header;
+ }
+
+ /**
+ * Sends the file.
+ *
+ * {@inheritdoc}
+ */
+ public function sendContent()
+ {
+ if (!$this->isSuccessful()) {
+ return parent::sendContent();
+ }
+
+ if (0 === $this->maxlen) {
+ return $this;
+ }
+
+ $out = fopen('php://output', 'wb');
+ $file = fopen($this->file->getPathname(), 'rb');
+
+ stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);
+
+ fclose($out);
+ fclose($file);
+
+ if ($this->deleteFileAfterSend) {
+ unlink($this->file->getPathname());
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \LogicException when the content is not null
+ */
+ public function setContent($content)
+ {
+ if (null !== $content) {
+ throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return false
+ */
+ public function getContent()
+ {
+ return false;
+ }
+
+ /**
+ * Trust X-Sendfile-Type header.
+ */
+ public static function trustXSendfileTypeHeader()
+ {
+ self::$trustXSendfileTypeHeader = true;
+ }
+
+ /**
+ * If this is set to true, the file will be unlinked after the request is send
+ * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
+ *
+ * @param bool $shouldDelete
+ *
+ * @return $this
+ */
+ public function deleteFileAfterSend($shouldDelete)
+ {
+ $this->deleteFileAfterSend = $shouldDelete;
+
+ return $this;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/CHANGELOG.md b/assets/php/vendor/symfony/http-foundation/CHANGELOG.md
new file mode 100644
index 0000000..ee5b6ce
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/CHANGELOG.md
@@ -0,0 +1,159 @@
+CHANGELOG
+=========
+
+3.4.0
+-----
+
+ * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new
+ `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper
+ * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes
+ * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
+ * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
+ * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
+
+3.3.0
+-----
+
+ * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument,
+ see http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html for more info,
+ * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods,
+ * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown,
+ disabling `Range` and `Content-Length` handling, switching to chunked encoding instead
+ * added the `Cookie::fromString()` method that allows to create a cookie from a
+ raw header string
+
+3.1.0
+-----
+
+ * Added support for creating `JsonResponse` with a string of JSON data
+
+3.0.0
+-----
+
+ * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY"
+
+2.8.0
+-----
+
+ * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and
+ will be removed in 3.0.
+
+2.6.0
+-----
+
+ * PdoSessionHandler changes
+ - implemented different session locking strategies to prevent loss of data by concurrent access to the same session
+ - [BC BREAK] save session data in a binary column without base64_encode
+ - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session
+ - implemented lazy connections that are only opened when a session is used by either passing a dsn string
+ explicitly or falling back to session.save_path ini setting
+ - added a createTable method that initializes a correctly defined table depending on the database vendor
+
+2.5.0
+-----
+
+ * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation
+ of the options used while encoding data to JSON format.
+
+2.4.0
+-----
+
+ * added RequestStack
+ * added Request::getEncodings()
+ * added accessors methods to session handlers
+
+2.3.0
+-----
+
+ * added support for ranges of IPs in trusted proxies
+ * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode)
+ * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler`
+ to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases
+ to verify that Exceptions are properly thrown when the PDO queries fail.
+
+2.2.0
+-----
+
+ * fixed the Request::create() precedence (URI information always take precedence now)
+ * added Request::getTrustedProxies()
+ * deprecated Request::isProxyTrusted()
+ * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects
+ * added a IpUtils class to check if an IP belongs to a CIDR
+ * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method)
+ * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to
+ enable it, and Request::getHttpMethodParameterOverride() to check if it is supported)
+ * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
+ * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3
+
+2.1.0
+-----
+
+ * added Request::getSchemeAndHttpHost() and Request::getUserInfo()
+ * added a fluent interface to the Response class
+ * added Request::isProxyTrusted()
+ * added JsonResponse
+ * added a getTargetUrl method to RedirectResponse
+ * added support for streamed responses
+ * made Response::prepare() method the place to enforce HTTP specification
+ * [BC BREAK] moved management of the locale from the Session class to the Request class
+ * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter()
+ * made FileBinaryMimeTypeGuesser command configurable
+ * added Request::getUser() and Request::getPassword()
+ * added support for the PATCH method in Request
+ * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3
+ * added ResponseHeaderBag::makeDisposition() (implements RFC 6266)
+ * made mimetype to extension conversion configurable
+ * [BC BREAK] Moved all session related classes and interfaces into own namespace, as
+ `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly.
+ Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`.
+ * SessionHandlers must implement `\SessionHandlerInterface` or extend from the
+ `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class.
+ * Added internal storage driver proxy mechanism for forward compatibility with
+ PHP 5.4 `\SessionHandler` class.
+ * Added session handlers for custom Memcache, Memcached and Null session save handlers.
+ * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`.
+ * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and
+ `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class
+ is a mediator for the session storage internals including the session handlers
+ which do the real work of participating in the internal PHP session workflow.
+ * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit
+ and functional testing without starting real PHP sessions. Removed
+ `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit
+ tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage`
+ for functional tests. These do not interact with global session ini
+ configuration values, session functions or `$_SESSION` superglobal. This means
+ they can be configured directly allowing multiple instances to work without
+ conflicting in the same PHP process.
+ * [BC BREAK] Removed the `close()` method from the `Session` class, as this is
+ now redundant.
+ * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()`
+ `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead
+ which returns a `FlashBagInterface`.
+ * `Session->clear()` now only clears session attributes as before it cleared
+ flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now.
+ * Session data is now managed by `SessionBagInterface` to better encapsulate
+ session data.
+ * Refactored session attribute and flash messages system to their own
+ `SessionBagInterface` implementations.
+ * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This
+ implementation is ESI compatible.
+ * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire
+ behaviour of messages auto expiring after one page page load. Messages must
+ be retrieved by `get()` or `all()`.
+ * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate
+ attributes storage behaviour from 2.0.x (default).
+ * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for
+ namespace session attributes.
+ * Flash API can stores messages in an array so there may be multiple messages
+ per flash type. The old `Session` class API remains without BC break as it
+ will allow single messages as before.
+ * Added basic session meta-data to the session to record session create time,
+ last updated time, and the lifetime of the session cookie that was provided
+ to the client.
+ * Request::getClientIp() method doesn't take a parameter anymore but bases
+ itself on the trustProxy parameter.
+ * Added isMethod() to Request object.
+ * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of
+ a `Request` now all return a raw value (vs a urldecoded value before). Any call
+ to one of these methods must be checked and wrapped in a `rawurldecode()` if
+ needed.
diff --git a/assets/php/vendor/symfony/http-foundation/Cookie.php b/assets/php/vendor/symfony/http-foundation/Cookie.php
new file mode 100644
index 0000000..4519a6a
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Cookie.php
@@ -0,0 +1,289 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents a cookie.
+ *
+ * @author Johannes M. Schmitt
+ */
+class Cookie
+{
+ protected $name;
+ protected $value;
+ protected $domain;
+ protected $expire;
+ protected $path;
+ protected $secure;
+ protected $httpOnly;
+ private $raw;
+ private $sameSite;
+
+ const SAMESITE_LAX = 'lax';
+ const SAMESITE_STRICT = 'strict';
+
+ /**
+ * Creates cookie from raw header string.
+ *
+ * @param string $cookie
+ * @param bool $decode
+ *
+ * @return static
+ */
+ public static function fromString($cookie, $decode = false)
+ {
+ $data = array(
+ 'expires' => 0,
+ 'path' => '/',
+ 'domain' => null,
+ 'secure' => false,
+ 'httponly' => false,
+ 'raw' => !$decode,
+ 'samesite' => null,
+ );
+ foreach (explode(';', $cookie) as $part) {
+ if (false === strpos($part, '=')) {
+ $key = trim($part);
+ $value = true;
+ } else {
+ list($key, $value) = explode('=', trim($part), 2);
+ $key = trim($key);
+ $value = trim($value);
+ }
+ if (!isset($data['name'])) {
+ $data['name'] = $decode ? urldecode($key) : $key;
+ $data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value);
+ continue;
+ }
+ switch ($key = strtolower($key)) {
+ case 'name':
+ case 'value':
+ break;
+ case 'max-age':
+ $data['expires'] = time() + (int) $value;
+ break;
+ default:
+ $data[$key] = $value;
+ break;
+ }
+ }
+
+ return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
+ }
+
+ /**
+ * @param string $name The name of the cookie
+ * @param string|null $value The value of the cookie
+ * @param int|string|\DateTimeInterface $expire The time the cookie expires
+ * @param string $path The path on the server in which the cookie will be available on
+ * @param string|null $domain The domain that the cookie is available to
+ * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
+ * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
+ * @param bool $raw Whether the cookie value should be sent with no url encoding
+ * @param string|null $sameSite Whether the cookie will be available for cross-site requests
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null)
+ {
+ // from PHP source code
+ if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
+ throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
+ }
+
+ if (empty($name)) {
+ throw new \InvalidArgumentException('The cookie name cannot be empty.');
+ }
+
+ // convert expiration time to a Unix timestamp
+ if ($expire instanceof \DateTimeInterface) {
+ $expire = $expire->format('U');
+ } elseif (!is_numeric($expire)) {
+ $expire = strtotime($expire);
+
+ if (false === $expire) {
+ throw new \InvalidArgumentException('The cookie expiration time is not valid.');
+ }
+ }
+
+ $this->name = $name;
+ $this->value = $value;
+ $this->domain = $domain;
+ $this->expire = 0 < $expire ? (int) $expire : 0;
+ $this->path = empty($path) ? '/' : $path;
+ $this->secure = (bool) $secure;
+ $this->httpOnly = (bool) $httpOnly;
+ $this->raw = (bool) $raw;
+
+ if (null !== $sameSite) {
+ $sameSite = strtolower($sameSite);
+ }
+
+ if (!in_array($sameSite, array(self::SAMESITE_LAX, self::SAMESITE_STRICT, null), true)) {
+ throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
+ }
+
+ $this->sameSite = $sameSite;
+ }
+
+ /**
+ * Returns the cookie as a string.
+ *
+ * @return string The cookie
+ */
+ public function __toString()
+ {
+ $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'=';
+
+ if ('' === (string) $this->getValue()) {
+ $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001';
+ } else {
+ $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
+
+ if (0 !== $this->getExpiresTime()) {
+ $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; max-age='.$this->getMaxAge();
+ }
+ }
+
+ if ($this->getPath()) {
+ $str .= '; path='.$this->getPath();
+ }
+
+ if ($this->getDomain()) {
+ $str .= '; domain='.$this->getDomain();
+ }
+
+ if (true === $this->isSecure()) {
+ $str .= '; secure';
+ }
+
+ if (true === $this->isHttpOnly()) {
+ $str .= '; httponly';
+ }
+
+ if (null !== $this->getSameSite()) {
+ $str .= '; samesite='.$this->getSameSite();
+ }
+
+ return $str;
+ }
+
+ /**
+ * Gets the name of the cookie.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Gets the value of the cookie.
+ *
+ * @return string|null
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Gets the domain that the cookie is available to.
+ *
+ * @return string|null
+ */
+ public function getDomain()
+ {
+ return $this->domain;
+ }
+
+ /**
+ * Gets the time the cookie expires.
+ *
+ * @return int
+ */
+ public function getExpiresTime()
+ {
+ return $this->expire;
+ }
+
+ /**
+ * Gets the max-age attribute.
+ *
+ * @return int
+ */
+ public function getMaxAge()
+ {
+ return 0 !== $this->expire ? $this->expire - time() : 0;
+ }
+
+ /**
+ * Gets the path on the server in which the cookie will be available on.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
+ *
+ * @return bool
+ */
+ public function isSecure()
+ {
+ return $this->secure;
+ }
+
+ /**
+ * Checks whether the cookie will be made accessible only through the HTTP protocol.
+ *
+ * @return bool
+ */
+ public function isHttpOnly()
+ {
+ return $this->httpOnly;
+ }
+
+ /**
+ * Whether this cookie is about to be cleared.
+ *
+ * @return bool
+ */
+ public function isCleared()
+ {
+ return $this->expire < time();
+ }
+
+ /**
+ * Checks if the cookie value should be sent with no url encoding.
+ *
+ * @return bool
+ */
+ public function isRaw()
+ {
+ return $this->raw;
+ }
+
+ /**
+ * Gets the SameSite attribute.
+ *
+ * @return string|null
+ */
+ public function getSameSite()
+ {
+ return $this->sameSite;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/assets/php/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php
new file mode 100644
index 0000000..5fcf5b4
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * The HTTP request contains headers with conflicting information.
+ *
+ * @author Magnus Nordlander
+ */
+class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/assets/php/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php
new file mode 100644
index 0000000..478d0dc
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Interface for Request exceptions.
+ *
+ * Exceptions implementing this interface should trigger an HTTP 400 response in the application code.
+ */
+interface RequestExceptionInterface
+{
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/assets/php/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php
new file mode 100644
index 0000000..ae7a5f1
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Raised when a user has performed an operation that should be considered
+ * suspicious from a security perspective.
+ */
+class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/assets/php/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/assets/php/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
new file mode 100644
index 0000000..e9c8441
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+/**
+ * ExpressionRequestMatcher uses an expression to match a Request.
+ *
+ * @author Fabien Potencier
+ */
+class ExpressionRequestMatcher extends RequestMatcher
+{
+ private $language;
+ private $expression;
+
+ public function setExpression(ExpressionLanguage $language, $expression)
+ {
+ $this->language = $language;
+ $this->expression = $expression;
+ }
+
+ public function matches(Request $request)
+ {
+ if (!$this->language) {
+ throw new \LogicException('Unable to match the request as the expression language is not available.');
+ }
+
+ return $this->language->evaluate($this->expression, array(
+ 'request' => $request,
+ 'method' => $request->getMethod(),
+ 'path' => rawurldecode($request->getPathInfo()),
+ 'host' => $request->getHost(),
+ 'ip' => $request->getClientIp(),
+ 'attributes' => $request->attributes->all(),
+ )) && parent::matches($request);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php
new file mode 100644
index 0000000..3b8e41d
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when the access on a file was denied.
+ *
+ * @author Bernhard Schussek
+ */
+class AccessDeniedException extends FileException
+{
+ /**
+ * @param string $path The path to the accessed file
+ */
+ public function __construct($path)
+ {
+ parent::__construct(sprintf('The file %s could not be accessed', $path));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/Exception/FileException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/FileException.php
new file mode 100644
index 0000000..fad5133
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/Exception/FileException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred in the component File.
+ *
+ * @author Bernhard Schussek
+ */
+class FileException extends \RuntimeException
+{
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php
new file mode 100644
index 0000000..bfcc37e
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when a file was not found.
+ *
+ * @author Bernhard Schussek
+ */
+class FileNotFoundException extends FileException
+{
+ /**
+ * @param string $path The path to the file that was not found
+ */
+ public function __construct($path)
+ {
+ parent::__construct(sprintf('The file "%s" does not exist', $path));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
new file mode 100644
index 0000000..0444b87
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+class UnexpectedTypeException extends FileException
+{
+ public function __construct($value, $expectedType)
+ {
+ parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/Exception/UploadException.php b/assets/php/vendor/symfony/http-foundation/File/Exception/UploadException.php
new file mode 100644
index 0000000..7074e76
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/Exception/UploadException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred during file upload.
+ *
+ * @author Bernhard Schussek
+ */
+class UploadException extends FileException
+{
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/File.php b/assets/php/vendor/symfony/http-foundation/File/File.php
new file mode 100644
index 0000000..e2a6768
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/File.php
@@ -0,0 +1,136 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
+
+/**
+ * A file in the file system.
+ *
+ * @author Bernhard Schussek
+ */
+class File extends \SplFileInfo
+{
+ /**
+ * Constructs a new file from the given path.
+ *
+ * @param string $path The path to the file
+ * @param bool $checkPath Whether to check the path or not
+ *
+ * @throws FileNotFoundException If the given path is not a file
+ */
+ public function __construct($path, $checkPath = true)
+ {
+ if ($checkPath && !is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ parent::__construct($path);
+ }
+
+ /**
+ * Returns the extension based on the mime type.
+ *
+ * If the mime type is unknown, returns null.
+ *
+ * This method uses the mime type as guessed by getMimeType()
+ * to guess the file extension.
+ *
+ * @return string|null The guessed extension or null if it cannot be guessed
+ *
+ * @see ExtensionGuesser
+ * @see getMimeType()
+ */
+ public function guessExtension()
+ {
+ $type = $this->getMimeType();
+ $guesser = ExtensionGuesser::getInstance();
+
+ return $guesser->guess($type);
+ }
+
+ /**
+ * Returns the mime type of the file.
+ *
+ * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(),
+ * mime_content_type() and the system binary "file" (in this order), depending on
+ * which of those are available.
+ *
+ * @return string|null The guessed mime type (e.g. "application/pdf")
+ *
+ * @see MimeTypeGuesser
+ */
+ public function getMimeType()
+ {
+ $guesser = MimeTypeGuesser::getInstance();
+
+ return $guesser->guess($this->getPathname());
+ }
+
+ /**
+ * Moves the file to a new location.
+ *
+ * @param string $directory The destination folder
+ * @param string $name The new file name
+ *
+ * @return self A File object representing the new file
+ *
+ * @throws FileException if the target file could not be created
+ */
+ public function move($directory, $name = null)
+ {
+ $target = $this->getTargetFile($directory, $name);
+
+ if (!@rename($this->getPathname(), $target)) {
+ $error = error_get_last();
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
+ }
+
+ @chmod($target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ protected function getTargetFile($directory, $name = null)
+ {
+ if (!is_dir($directory)) {
+ if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
+ throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
+ }
+ } elseif (!is_writable($directory)) {
+ throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
+ }
+
+ $target = rtrim($directory, '/\\').DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
+
+ return new self($target, false);
+ }
+
+ /**
+ * Returns locale independent base name of the given path.
+ *
+ * @param string $name The new file name
+ *
+ * @return string containing
+ */
+ protected function getName($name)
+ {
+ $originalName = str_replace('\\', '/', $name);
+ $pos = strrpos($originalName, '/');
+ $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
+
+ return $originalName;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php
new file mode 100644
index 0000000..263fb32
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * A singleton mime type to file extension guesser.
+ *
+ * A default guesser is provided.
+ * You can register custom guessers by calling the register()
+ * method on the singleton instance:
+ *
+ * $guesser = ExtensionGuesser::getInstance();
+ * $guesser->register(new MyCustomExtensionGuesser());
+ *
+ * The last registered guesser is preferred over previously registered ones.
+ */
+class ExtensionGuesser implements ExtensionGuesserInterface
+{
+ /**
+ * The singleton instance.
+ *
+ * @var ExtensionGuesser
+ */
+ private static $instance = null;
+
+ /**
+ * All registered ExtensionGuesserInterface instances.
+ *
+ * @var array
+ */
+ protected $guessers = array();
+
+ /**
+ * Returns the singleton instance.
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (null === self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Registers all natively provided extension guessers.
+ */
+ private function __construct()
+ {
+ $this->register(new MimeTypeExtensionGuesser());
+ }
+
+ /**
+ * Registers a new extension guesser.
+ *
+ * When guessing, this guesser is preferred over previously registered ones.
+ */
+ public function register(ExtensionGuesserInterface $guesser)
+ {
+ array_unshift($this->guessers, $guesser);
+ }
+
+ /**
+ * Tries to guess the extension.
+ *
+ * The mime type is passed to each registered mime type guesser in reverse order
+ * of their registration (last registered is queried first). Once a guesser
+ * returns a value that is not NULL, this method terminates and returns the
+ * value.
+ *
+ * @param string $mimeType The mime type
+ *
+ * @return string The guessed extension or NULL, if none could be guessed
+ */
+ public function guess($mimeType)
+ {
+ foreach ($this->guessers as $guesser) {
+ if (null !== $extension = $guesser->guess($mimeType)) {
+ return $extension;
+ }
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php
new file mode 100644
index 0000000..d19a0e5
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * Guesses the file extension corresponding to a given mime type.
+ */
+interface ExtensionGuesserInterface
+{
+ /**
+ * Makes a best guess for a file extension, given a mime type.
+ *
+ * @param string $mimeType The mime type
+ *
+ * @return string The guessed extension or NULL, if none could be guessed
+ */
+ public function guess($mimeType);
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php
new file mode 100644
index 0000000..c2ac676
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+
+/**
+ * Guesses the mime type with the binary "file" (only available on *nix).
+ *
+ * @author Bernhard Schussek
+ */
+class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ private $cmd;
+
+ /**
+ * The $cmd pattern must contain a "%s" string that will be replaced
+ * with the file name to guess.
+ *
+ * The command output must start with the mime type of the file.
+ *
+ * @param string $cmd The command to run to get the mime type of a file
+ */
+ public function __construct($cmd = 'file -b --mime %s 2>/dev/null')
+ {
+ $this->cmd = $cmd;
+ }
+
+ /**
+ * Returns whether this guesser is supported on the current OS.
+ *
+ * @return bool
+ */
+ public static function isSupported()
+ {
+ return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ if (!self::isSupported()) {
+ return;
+ }
+
+ ob_start();
+
+ // need to use --mime instead of -i. see #6641
+ passthru(sprintf($this->cmd, escapeshellarg($path)), $return);
+ if ($return > 0) {
+ ob_end_clean();
+
+ return;
+ }
+
+ $type = trim(ob_get_clean());
+
+ if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
+ // it's not a type, but an error message
+ return;
+ }
+
+ return $match[1];
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php
new file mode 100644
index 0000000..9b42835
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+
+/**
+ * Guesses the mime type using the PECL extension FileInfo.
+ *
+ * @author Bernhard Schussek
+ */
+class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ private $magicFile;
+
+ /**
+ * @param string $magicFile A magic file to use with the finfo instance
+ *
+ * @see http://www.php.net/manual/en/function.finfo-open.php
+ */
+ public function __construct($magicFile = null)
+ {
+ $this->magicFile = $magicFile;
+ }
+
+ /**
+ * Returns whether this guesser is supported on the current OS/PHP setup.
+ *
+ * @return bool
+ */
+ public static function isSupported()
+ {
+ return function_exists('finfo_open');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ if (!self::isSupported()) {
+ return;
+ }
+
+ if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
+ return;
+ }
+
+ return $finfo->file($path);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php
new file mode 100644
index 0000000..77bf51b
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php
@@ -0,0 +1,808 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * Provides a best-guess mapping of mime type to file extension.
+ */
+class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
+{
+ /**
+ * A map of mime types and their default extensions.
+ *
+ * This list has been placed under the public domain by the Apache HTTPD project.
+ * This list has been updated from upstream on 2013-04-23.
+ *
+ * @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
+ */
+ protected $defaultExtensions = array(
+ 'application/andrew-inset' => 'ez',
+ 'application/applixware' => 'aw',
+ 'application/atom+xml' => 'atom',
+ 'application/atomcat+xml' => 'atomcat',
+ 'application/atomsvc+xml' => 'atomsvc',
+ 'application/ccxml+xml' => 'ccxml',
+ 'application/cdmi-capability' => 'cdmia',
+ 'application/cdmi-container' => 'cdmic',
+ 'application/cdmi-domain' => 'cdmid',
+ 'application/cdmi-object' => 'cdmio',
+ 'application/cdmi-queue' => 'cdmiq',
+ 'application/cu-seeme' => 'cu',
+ 'application/davmount+xml' => 'davmount',
+ 'application/docbook+xml' => 'dbk',
+ 'application/dssc+der' => 'dssc',
+ 'application/dssc+xml' => 'xdssc',
+ 'application/ecmascript' => 'ecma',
+ 'application/emma+xml' => 'emma',
+ 'application/epub+zip' => 'epub',
+ 'application/exi' => 'exi',
+ 'application/font-tdpfr' => 'pfr',
+ 'application/gml+xml' => 'gml',
+ 'application/gpx+xml' => 'gpx',
+ 'application/gxf' => 'gxf',
+ 'application/hyperstudio' => 'stk',
+ 'application/inkml+xml' => 'ink',
+ 'application/ipfix' => 'ipfix',
+ 'application/java-archive' => 'jar',
+ 'application/java-serialized-object' => 'ser',
+ 'application/java-vm' => 'class',
+ 'application/javascript' => 'js',
+ 'application/json' => 'json',
+ 'application/jsonml+json' => 'jsonml',
+ 'application/lost+xml' => 'lostxml',
+ 'application/mac-binhex40' => 'hqx',
+ 'application/mac-compactpro' => 'cpt',
+ 'application/mads+xml' => 'mads',
+ 'application/marc' => 'mrc',
+ 'application/marcxml+xml' => 'mrcx',
+ 'application/mathematica' => 'ma',
+ 'application/mathml+xml' => 'mathml',
+ 'application/mbox' => 'mbox',
+ 'application/mediaservercontrol+xml' => 'mscml',
+ 'application/metalink+xml' => 'metalink',
+ 'application/metalink4+xml' => 'meta4',
+ 'application/mets+xml' => 'mets',
+ 'application/mods+xml' => 'mods',
+ 'application/mp21' => 'm21',
+ 'application/mp4' => 'mp4s',
+ 'application/msword' => 'doc',
+ 'application/mxf' => 'mxf',
+ 'application/octet-stream' => 'bin',
+ 'application/oda' => 'oda',
+ 'application/oebps-package+xml' => 'opf',
+ 'application/ogg' => 'ogx',
+ 'application/omdoc+xml' => 'omdoc',
+ 'application/onenote' => 'onetoc',
+ 'application/oxps' => 'oxps',
+ 'application/patch-ops-error+xml' => 'xer',
+ 'application/pdf' => 'pdf',
+ 'application/pgp-encrypted' => 'pgp',
+ 'application/pgp-signature' => 'asc',
+ 'application/pics-rules' => 'prf',
+ 'application/pkcs10' => 'p10',
+ 'application/pkcs7-mime' => 'p7m',
+ 'application/pkcs7-signature' => 'p7s',
+ 'application/pkcs8' => 'p8',
+ 'application/pkix-attr-cert' => 'ac',
+ 'application/pkix-cert' => 'cer',
+ 'application/pkix-crl' => 'crl',
+ 'application/pkix-pkipath' => 'pkipath',
+ 'application/pkixcmp' => 'pki',
+ 'application/pls+xml' => 'pls',
+ 'application/postscript' => 'ai',
+ 'application/prs.cww' => 'cww',
+ 'application/pskc+xml' => 'pskcxml',
+ 'application/rdf+xml' => 'rdf',
+ 'application/reginfo+xml' => 'rif',
+ 'application/relax-ng-compact-syntax' => 'rnc',
+ 'application/resource-lists+xml' => 'rl',
+ 'application/resource-lists-diff+xml' => 'rld',
+ 'application/rls-services+xml' => 'rs',
+ 'application/rpki-ghostbusters' => 'gbr',
+ 'application/rpki-manifest' => 'mft',
+ 'application/rpki-roa' => 'roa',
+ 'application/rsd+xml' => 'rsd',
+ 'application/rss+xml' => 'rss',
+ 'application/rtf' => 'rtf',
+ 'application/sbml+xml' => 'sbml',
+ 'application/scvp-cv-request' => 'scq',
+ 'application/scvp-cv-response' => 'scs',
+ 'application/scvp-vp-request' => 'spq',
+ 'application/scvp-vp-response' => 'spp',
+ 'application/sdp' => 'sdp',
+ 'application/set-payment-initiation' => 'setpay',
+ 'application/set-registration-initiation' => 'setreg',
+ 'application/shf+xml' => 'shf',
+ 'application/smil+xml' => 'smi',
+ 'application/sparql-query' => 'rq',
+ 'application/sparql-results+xml' => 'srx',
+ 'application/srgs' => 'gram',
+ 'application/srgs+xml' => 'grxml',
+ 'application/sru+xml' => 'sru',
+ 'application/ssdl+xml' => 'ssdl',
+ 'application/ssml+xml' => 'ssml',
+ 'application/tei+xml' => 'tei',
+ 'application/thraud+xml' => 'tfi',
+ 'application/timestamped-data' => 'tsd',
+ 'application/vnd.3gpp.pic-bw-large' => 'plb',
+ 'application/vnd.3gpp.pic-bw-small' => 'psb',
+ 'application/vnd.3gpp.pic-bw-var' => 'pvb',
+ 'application/vnd.3gpp2.tcap' => 'tcap',
+ 'application/vnd.3m.post-it-notes' => 'pwn',
+ 'application/vnd.accpac.simply.aso' => 'aso',
+ 'application/vnd.accpac.simply.imp' => 'imp',
+ 'application/vnd.acucobol' => 'acu',
+ 'application/vnd.acucorp' => 'atc',
+ 'application/vnd.adobe.air-application-installer-package+zip' => 'air',
+ 'application/vnd.adobe.formscentral.fcdt' => 'fcdt',
+ 'application/vnd.adobe.fxp' => 'fxp',
+ 'application/vnd.adobe.xdp+xml' => 'xdp',
+ 'application/vnd.adobe.xfdf' => 'xfdf',
+ 'application/vnd.ahead.space' => 'ahead',
+ 'application/vnd.airzip.filesecure.azf' => 'azf',
+ 'application/vnd.airzip.filesecure.azs' => 'azs',
+ 'application/vnd.amazon.ebook' => 'azw',
+ 'application/vnd.americandynamics.acc' => 'acc',
+ 'application/vnd.amiga.ami' => 'ami',
+ 'application/vnd.android.package-archive' => 'apk',
+ 'application/vnd.anser-web-certificate-issue-initiation' => 'cii',
+ 'application/vnd.anser-web-funds-transfer-initiation' => 'fti',
+ 'application/vnd.antix.game-component' => 'atx',
+ 'application/vnd.apple.installer+xml' => 'mpkg',
+ 'application/vnd.apple.mpegurl' => 'm3u8',
+ 'application/vnd.aristanetworks.swi' => 'swi',
+ 'application/vnd.astraea-software.iota' => 'iota',
+ 'application/vnd.audiograph' => 'aep',
+ 'application/vnd.blueice.multipass' => 'mpm',
+ 'application/vnd.bmi' => 'bmi',
+ 'application/vnd.businessobjects' => 'rep',
+ 'application/vnd.chemdraw+xml' => 'cdxml',
+ 'application/vnd.chipnuts.karaoke-mmd' => 'mmd',
+ 'application/vnd.cinderella' => 'cdy',
+ 'application/vnd.claymore' => 'cla',
+ 'application/vnd.cloanto.rp9' => 'rp9',
+ 'application/vnd.clonk.c4group' => 'c4g',
+ 'application/vnd.cluetrust.cartomobile-config' => 'c11amc',
+ 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz',
+ 'application/vnd.commonspace' => 'csp',
+ 'application/vnd.contact.cmsg' => 'cdbcmsg',
+ 'application/vnd.cosmocaller' => 'cmc',
+ 'application/vnd.crick.clicker' => 'clkx',
+ 'application/vnd.crick.clicker.keyboard' => 'clkk',
+ 'application/vnd.crick.clicker.palette' => 'clkp',
+ 'application/vnd.crick.clicker.template' => 'clkt',
+ 'application/vnd.crick.clicker.wordbank' => 'clkw',
+ 'application/vnd.criticaltools.wbs+xml' => 'wbs',
+ 'application/vnd.ctc-posml' => 'pml',
+ 'application/vnd.cups-ppd' => 'ppd',
+ 'application/vnd.curl.car' => 'car',
+ 'application/vnd.curl.pcurl' => 'pcurl',
+ 'application/vnd.dart' => 'dart',
+ 'application/vnd.data-vision.rdz' => 'rdz',
+ 'application/vnd.dece.data' => 'uvf',
+ 'application/vnd.dece.ttml+xml' => 'uvt',
+ 'application/vnd.dece.unspecified' => 'uvx',
+ 'application/vnd.dece.zip' => 'uvz',
+ 'application/vnd.denovo.fcselayout-link' => 'fe_launch',
+ 'application/vnd.dna' => 'dna',
+ 'application/vnd.dolby.mlp' => 'mlp',
+ 'application/vnd.dpgraph' => 'dpg',
+ 'application/vnd.dreamfactory' => 'dfac',
+ 'application/vnd.ds-keypoint' => 'kpxx',
+ 'application/vnd.dvb.ait' => 'ait',
+ 'application/vnd.dvb.service' => 'svc',
+ 'application/vnd.dynageo' => 'geo',
+ 'application/vnd.ecowin.chart' => 'mag',
+ 'application/vnd.enliven' => 'nml',
+ 'application/vnd.epson.esf' => 'esf',
+ 'application/vnd.epson.msf' => 'msf',
+ 'application/vnd.epson.quickanime' => 'qam',
+ 'application/vnd.epson.salt' => 'slt',
+ 'application/vnd.epson.ssf' => 'ssf',
+ 'application/vnd.eszigno3+xml' => 'es3',
+ 'application/vnd.ezpix-album' => 'ez2',
+ 'application/vnd.ezpix-package' => 'ez3',
+ 'application/vnd.fdf' => 'fdf',
+ 'application/vnd.fdsn.mseed' => 'mseed',
+ 'application/vnd.fdsn.seed' => 'seed',
+ 'application/vnd.flographit' => 'gph',
+ 'application/vnd.fluxtime.clip' => 'ftc',
+ 'application/vnd.framemaker' => 'fm',
+ 'application/vnd.frogans.fnc' => 'fnc',
+ 'application/vnd.frogans.ltf' => 'ltf',
+ 'application/vnd.fsc.weblaunch' => 'fsc',
+ 'application/vnd.fujitsu.oasys' => 'oas',
+ 'application/vnd.fujitsu.oasys2' => 'oa2',
+ 'application/vnd.fujitsu.oasys3' => 'oa3',
+ 'application/vnd.fujitsu.oasysgp' => 'fg5',
+ 'application/vnd.fujitsu.oasysprs' => 'bh2',
+ 'application/vnd.fujixerox.ddd' => 'ddd',
+ 'application/vnd.fujixerox.docuworks' => 'xdw',
+ 'application/vnd.fujixerox.docuworks.binder' => 'xbd',
+ 'application/vnd.fuzzysheet' => 'fzs',
+ 'application/vnd.genomatix.tuxedo' => 'txd',
+ 'application/vnd.geogebra.file' => 'ggb',
+ 'application/vnd.geogebra.tool' => 'ggt',
+ 'application/vnd.geometry-explorer' => 'gex',
+ 'application/vnd.geonext' => 'gxt',
+ 'application/vnd.geoplan' => 'g2w',
+ 'application/vnd.geospace' => 'g3w',
+ 'application/vnd.gmx' => 'gmx',
+ 'application/vnd.google-earth.kml+xml' => 'kml',
+ 'application/vnd.google-earth.kmz' => 'kmz',
+ 'application/vnd.grafeq' => 'gqf',
+ 'application/vnd.groove-account' => 'gac',
+ 'application/vnd.groove-help' => 'ghf',
+ 'application/vnd.groove-identity-message' => 'gim',
+ 'application/vnd.groove-injector' => 'grv',
+ 'application/vnd.groove-tool-message' => 'gtm',
+ 'application/vnd.groove-tool-template' => 'tpl',
+ 'application/vnd.groove-vcard' => 'vcg',
+ 'application/vnd.hal+xml' => 'hal',
+ 'application/vnd.handheld-entertainment+xml' => 'zmm',
+ 'application/vnd.hbci' => 'hbci',
+ 'application/vnd.hhe.lesson-player' => 'les',
+ 'application/vnd.hp-hpgl' => 'hpgl',
+ 'application/vnd.hp-hpid' => 'hpid',
+ 'application/vnd.hp-hps' => 'hps',
+ 'application/vnd.hp-jlyt' => 'jlt',
+ 'application/vnd.hp-pcl' => 'pcl',
+ 'application/vnd.hp-pclxl' => 'pclxl',
+ 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
+ 'application/vnd.ibm.minipay' => 'mpy',
+ 'application/vnd.ibm.modcap' => 'afp',
+ 'application/vnd.ibm.rights-management' => 'irm',
+ 'application/vnd.ibm.secure-container' => 'sc',
+ 'application/vnd.iccprofile' => 'icc',
+ 'application/vnd.igloader' => 'igl',
+ 'application/vnd.immervision-ivp' => 'ivp',
+ 'application/vnd.immervision-ivu' => 'ivu',
+ 'application/vnd.insors.igm' => 'igm',
+ 'application/vnd.intercon.formnet' => 'xpw',
+ 'application/vnd.intergeo' => 'i2g',
+ 'application/vnd.intu.qbo' => 'qbo',
+ 'application/vnd.intu.qfx' => 'qfx',
+ 'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
+ 'application/vnd.irepository.package+xml' => 'irp',
+ 'application/vnd.is-xpr' => 'xpr',
+ 'application/vnd.isac.fcs' => 'fcs',
+ 'application/vnd.jam' => 'jam',
+ 'application/vnd.jcp.javame.midlet-rms' => 'rms',
+ 'application/vnd.jisp' => 'jisp',
+ 'application/vnd.joost.joda-archive' => 'joda',
+ 'application/vnd.kahootz' => 'ktz',
+ 'application/vnd.kde.karbon' => 'karbon',
+ 'application/vnd.kde.kchart' => 'chrt',
+ 'application/vnd.kde.kformula' => 'kfo',
+ 'application/vnd.kde.kivio' => 'flw',
+ 'application/vnd.kde.kontour' => 'kon',
+ 'application/vnd.kde.kpresenter' => 'kpr',
+ 'application/vnd.kde.kspread' => 'ksp',
+ 'application/vnd.kde.kword' => 'kwd',
+ 'application/vnd.kenameaapp' => 'htke',
+ 'application/vnd.kidspiration' => 'kia',
+ 'application/vnd.kinar' => 'kne',
+ 'application/vnd.koan' => 'skp',
+ 'application/vnd.kodak-descriptor' => 'sse',
+ 'application/vnd.las.las+xml' => 'lasxml',
+ 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
+ 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
+ 'application/vnd.lotus-1-2-3' => '123',
+ 'application/vnd.lotus-approach' => 'apr',
+ 'application/vnd.lotus-freelance' => 'pre',
+ 'application/vnd.lotus-notes' => 'nsf',
+ 'application/vnd.lotus-organizer' => 'org',
+ 'application/vnd.lotus-screencam' => 'scm',
+ 'application/vnd.lotus-wordpro' => 'lwp',
+ 'application/vnd.macports.portpkg' => 'portpkg',
+ 'application/vnd.mcd' => 'mcd',
+ 'application/vnd.medcalcdata' => 'mc1',
+ 'application/vnd.mediastation.cdkey' => 'cdkey',
+ 'application/vnd.mfer' => 'mwf',
+ 'application/vnd.mfmp' => 'mfm',
+ 'application/vnd.micrografx.flo' => 'flo',
+ 'application/vnd.micrografx.igx' => 'igx',
+ 'application/vnd.mif' => 'mif',
+ 'application/vnd.mobius.daf' => 'daf',
+ 'application/vnd.mobius.dis' => 'dis',
+ 'application/vnd.mobius.mbk' => 'mbk',
+ 'application/vnd.mobius.mqy' => 'mqy',
+ 'application/vnd.mobius.msl' => 'msl',
+ 'application/vnd.mobius.plc' => 'plc',
+ 'application/vnd.mobius.txf' => 'txf',
+ 'application/vnd.mophun.application' => 'mpn',
+ 'application/vnd.mophun.certificate' => 'mpc',
+ 'application/vnd.mozilla.xul+xml' => 'xul',
+ 'application/vnd.ms-artgalry' => 'cil',
+ 'application/vnd.ms-cab-compressed' => 'cab',
+ 'application/vnd.ms-excel' => 'xls',
+ 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
+ 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
+ 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
+ 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
+ 'application/vnd.ms-fontobject' => 'eot',
+ 'application/vnd.ms-htmlhelp' => 'chm',
+ 'application/vnd.ms-ims' => 'ims',
+ 'application/vnd.ms-lrm' => 'lrm',
+ 'application/vnd.ms-officetheme' => 'thmx',
+ 'application/vnd.ms-pki.seccat' => 'cat',
+ 'application/vnd.ms-pki.stl' => 'stl',
+ 'application/vnd.ms-powerpoint' => 'ppt',
+ 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
+ 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
+ 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
+ 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
+ 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
+ 'application/vnd.ms-project' => 'mpp',
+ 'application/vnd.ms-word.document.macroenabled.12' => 'docm',
+ 'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
+ 'application/vnd.ms-works' => 'wps',
+ 'application/vnd.ms-wpl' => 'wpl',
+ 'application/vnd.ms-xpsdocument' => 'xps',
+ 'application/vnd.mseq' => 'mseq',
+ 'application/vnd.musician' => 'mus',
+ 'application/vnd.muvee.style' => 'msty',
+ 'application/vnd.mynfc' => 'taglet',
+ 'application/vnd.neurolanguage.nlu' => 'nlu',
+ 'application/vnd.nitf' => 'ntf',
+ 'application/vnd.noblenet-directory' => 'nnd',
+ 'application/vnd.noblenet-sealer' => 'nns',
+ 'application/vnd.noblenet-web' => 'nnw',
+ 'application/vnd.nokia.n-gage.data' => 'ngdat',
+ 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage',
+ 'application/vnd.nokia.radio-preset' => 'rpst',
+ 'application/vnd.nokia.radio-presets' => 'rpss',
+ 'application/vnd.novadigm.edm' => 'edm',
+ 'application/vnd.novadigm.edx' => 'edx',
+ 'application/vnd.novadigm.ext' => 'ext',
+ 'application/vnd.oasis.opendocument.chart' => 'odc',
+ 'application/vnd.oasis.opendocument.chart-template' => 'otc',
+ 'application/vnd.oasis.opendocument.database' => 'odb',
+ 'application/vnd.oasis.opendocument.formula' => 'odf',
+ 'application/vnd.oasis.opendocument.formula-template' => 'odft',
+ 'application/vnd.oasis.opendocument.graphics' => 'odg',
+ 'application/vnd.oasis.opendocument.graphics-template' => 'otg',
+ 'application/vnd.oasis.opendocument.image' => 'odi',
+ 'application/vnd.oasis.opendocument.image-template' => 'oti',
+ 'application/vnd.oasis.opendocument.presentation' => 'odp',
+ 'application/vnd.oasis.opendocument.presentation-template' => 'otp',
+ 'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
+ 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
+ 'application/vnd.oasis.opendocument.text' => 'odt',
+ 'application/vnd.oasis.opendocument.text-master' => 'odm',
+ 'application/vnd.oasis.opendocument.text-template' => 'ott',
+ 'application/vnd.oasis.opendocument.text-web' => 'oth',
+ 'application/vnd.olpc-sugar' => 'xo',
+ 'application/vnd.oma.dd2+xml' => 'dd2',
+ 'application/vnd.openofficeorg.extension' => 'oxt',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
+ 'application/vnd.osgeo.mapguide.package' => 'mgp',
+ 'application/vnd.osgi.dp' => 'dp',
+ 'application/vnd.osgi.subsystem' => 'esa',
+ 'application/vnd.palm' => 'pdb',
+ 'application/vnd.pawaafile' => 'paw',
+ 'application/vnd.pg.format' => 'str',
+ 'application/vnd.pg.osasli' => 'ei6',
+ 'application/vnd.picsel' => 'efif',
+ 'application/vnd.pmi.widget' => 'wg',
+ 'application/vnd.pocketlearn' => 'plf',
+ 'application/vnd.powerbuilder6' => 'pbd',
+ 'application/vnd.previewsystems.box' => 'box',
+ 'application/vnd.proteus.magazine' => 'mgz',
+ 'application/vnd.publishare-delta-tree' => 'qps',
+ 'application/vnd.pvi.ptid1' => 'ptid',
+ 'application/vnd.quark.quarkxpress' => 'qxd',
+ 'application/vnd.realvnc.bed' => 'bed',
+ 'application/vnd.recordare.musicxml' => 'mxl',
+ 'application/vnd.recordare.musicxml+xml' => 'musicxml',
+ 'application/vnd.rig.cryptonote' => 'cryptonote',
+ 'application/vnd.rim.cod' => 'cod',
+ 'application/vnd.rn-realmedia' => 'rm',
+ 'application/vnd.rn-realmedia-vbr' => 'rmvb',
+ 'application/vnd.route66.link66+xml' => 'link66',
+ 'application/vnd.sailingtracker.track' => 'st',
+ 'application/vnd.seemail' => 'see',
+ 'application/vnd.sema' => 'sema',
+ 'application/vnd.semd' => 'semd',
+ 'application/vnd.semf' => 'semf',
+ 'application/vnd.shana.informed.formdata' => 'ifm',
+ 'application/vnd.shana.informed.formtemplate' => 'itp',
+ 'application/vnd.shana.informed.interchange' => 'iif',
+ 'application/vnd.shana.informed.package' => 'ipk',
+ 'application/vnd.simtech-mindmapper' => 'twd',
+ 'application/vnd.smaf' => 'mmf',
+ 'application/vnd.smart.teacher' => 'teacher',
+ 'application/vnd.solent.sdkm+xml' => 'sdkm',
+ 'application/vnd.spotfire.dxp' => 'dxp',
+ 'application/vnd.spotfire.sfs' => 'sfs',
+ 'application/vnd.stardivision.calc' => 'sdc',
+ 'application/vnd.stardivision.draw' => 'sda',
+ 'application/vnd.stardivision.impress' => 'sdd',
+ 'application/vnd.stardivision.math' => 'smf',
+ 'application/vnd.stardivision.writer' => 'sdw',
+ 'application/vnd.stardivision.writer-global' => 'sgl',
+ 'application/vnd.stepmania.package' => 'smzip',
+ 'application/vnd.stepmania.stepchart' => 'sm',
+ 'application/vnd.sun.xml.calc' => 'sxc',
+ 'application/vnd.sun.xml.calc.template' => 'stc',
+ 'application/vnd.sun.xml.draw' => 'sxd',
+ 'application/vnd.sun.xml.draw.template' => 'std',
+ 'application/vnd.sun.xml.impress' => 'sxi',
+ 'application/vnd.sun.xml.impress.template' => 'sti',
+ 'application/vnd.sun.xml.math' => 'sxm',
+ 'application/vnd.sun.xml.writer' => 'sxw',
+ 'application/vnd.sun.xml.writer.global' => 'sxg',
+ 'application/vnd.sun.xml.writer.template' => 'stw',
+ 'application/vnd.sus-calendar' => 'sus',
+ 'application/vnd.svd' => 'svd',
+ 'application/vnd.symbian.install' => 'sis',
+ 'application/vnd.syncml+xml' => 'xsm',
+ 'application/vnd.syncml.dm+wbxml' => 'bdm',
+ 'application/vnd.syncml.dm+xml' => 'xdm',
+ 'application/vnd.tao.intent-module-archive' => 'tao',
+ 'application/vnd.tcpdump.pcap' => 'pcap',
+ 'application/vnd.tmobile-livetv' => 'tmo',
+ 'application/vnd.trid.tpt' => 'tpt',
+ 'application/vnd.triscape.mxs' => 'mxs',
+ 'application/vnd.trueapp' => 'tra',
+ 'application/vnd.ufdl' => 'ufd',
+ 'application/vnd.uiq.theme' => 'utz',
+ 'application/vnd.umajin' => 'umj',
+ 'application/vnd.unity' => 'unityweb',
+ 'application/vnd.uoml+xml' => 'uoml',
+ 'application/vnd.vcx' => 'vcx',
+ 'application/vnd.visio' => 'vsd',
+ 'application/vnd.visionary' => 'vis',
+ 'application/vnd.vsf' => 'vsf',
+ 'application/vnd.wap.wbxml' => 'wbxml',
+ 'application/vnd.wap.wmlc' => 'wmlc',
+ 'application/vnd.wap.wmlscriptc' => 'wmlsc',
+ 'application/vnd.webturbo' => 'wtb',
+ 'application/vnd.wolfram.player' => 'nbp',
+ 'application/vnd.wordperfect' => 'wpd',
+ 'application/vnd.wqd' => 'wqd',
+ 'application/vnd.wt.stf' => 'stf',
+ 'application/vnd.xara' => 'xar',
+ 'application/vnd.xfdl' => 'xfdl',
+ 'application/vnd.yamaha.hv-dic' => 'hvd',
+ 'application/vnd.yamaha.hv-script' => 'hvs',
+ 'application/vnd.yamaha.hv-voice' => 'hvp',
+ 'application/vnd.yamaha.openscoreformat' => 'osf',
+ 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg',
+ 'application/vnd.yamaha.smaf-audio' => 'saf',
+ 'application/vnd.yamaha.smaf-phrase' => 'spf',
+ 'application/vnd.yellowriver-custom-menu' => 'cmp',
+ 'application/vnd.zul' => 'zir',
+ 'application/vnd.zzazz.deck+xml' => 'zaz',
+ 'application/voicexml+xml' => 'vxml',
+ 'application/widget' => 'wgt',
+ 'application/winhlp' => 'hlp',
+ 'application/wsdl+xml' => 'wsdl',
+ 'application/wspolicy+xml' => 'wspolicy',
+ 'application/x-7z-compressed' => '7z',
+ 'application/x-abiword' => 'abw',
+ 'application/x-ace-compressed' => 'ace',
+ 'application/x-apple-diskimage' => 'dmg',
+ 'application/x-authorware-bin' => 'aab',
+ 'application/x-authorware-map' => 'aam',
+ 'application/x-authorware-seg' => 'aas',
+ 'application/x-bcpio' => 'bcpio',
+ 'application/x-bittorrent' => 'torrent',
+ 'application/x-blorb' => 'blb',
+ 'application/x-bzip' => 'bz',
+ 'application/x-bzip2' => 'bz2',
+ 'application/x-cbr' => 'cbr',
+ 'application/x-cdlink' => 'vcd',
+ 'application/x-cfs-compressed' => 'cfs',
+ 'application/x-chat' => 'chat',
+ 'application/x-chess-pgn' => 'pgn',
+ 'application/x-conference' => 'nsc',
+ 'application/x-cpio' => 'cpio',
+ 'application/x-csh' => 'csh',
+ 'application/x-debian-package' => 'deb',
+ 'application/x-dgc-compressed' => 'dgc',
+ 'application/x-director' => 'dir',
+ 'application/x-doom' => 'wad',
+ 'application/x-dtbncx+xml' => 'ncx',
+ 'application/x-dtbook+xml' => 'dtb',
+ 'application/x-dtbresource+xml' => 'res',
+ 'application/x-dvi' => 'dvi',
+ 'application/x-envoy' => 'evy',
+ 'application/x-eva' => 'eva',
+ 'application/x-font-bdf' => 'bdf',
+ 'application/x-font-ghostscript' => 'gsf',
+ 'application/x-font-linux-psf' => 'psf',
+ 'application/x-font-otf' => 'otf',
+ 'application/x-font-pcf' => 'pcf',
+ 'application/x-font-snf' => 'snf',
+ 'application/x-font-ttf' => 'ttf',
+ 'application/x-font-type1' => 'pfa',
+ 'application/x-font-woff' => 'woff',
+ 'application/x-freearc' => 'arc',
+ 'application/x-futuresplash' => 'spl',
+ 'application/x-gca-compressed' => 'gca',
+ 'application/x-glulx' => 'ulx',
+ 'application/x-gnumeric' => 'gnumeric',
+ 'application/x-gramps-xml' => 'gramps',
+ 'application/x-gtar' => 'gtar',
+ 'application/x-hdf' => 'hdf',
+ 'application/x-install-instructions' => 'install',
+ 'application/x-iso9660-image' => 'iso',
+ 'application/x-java-jnlp-file' => 'jnlp',
+ 'application/x-latex' => 'latex',
+ 'application/x-lzh-compressed' => 'lzh',
+ 'application/x-mie' => 'mie',
+ 'application/x-mobipocket-ebook' => 'prc',
+ 'application/x-ms-application' => 'application',
+ 'application/x-ms-shortcut' => 'lnk',
+ 'application/x-ms-wmd' => 'wmd',
+ 'application/x-ms-wmz' => 'wmz',
+ 'application/x-ms-xbap' => 'xbap',
+ 'application/x-msaccess' => 'mdb',
+ 'application/x-msbinder' => 'obd',
+ 'application/x-mscardfile' => 'crd',
+ 'application/x-msclip' => 'clp',
+ 'application/x-msdownload' => 'exe',
+ 'application/x-msmediaview' => 'mvb',
+ 'application/x-msmetafile' => 'wmf',
+ 'application/x-msmoney' => 'mny',
+ 'application/x-mspublisher' => 'pub',
+ 'application/x-msschedule' => 'scd',
+ 'application/x-msterminal' => 'trm',
+ 'application/x-mswrite' => 'wri',
+ 'application/x-netcdf' => 'nc',
+ 'application/x-nzb' => 'nzb',
+ 'application/x-pkcs12' => 'p12',
+ 'application/x-pkcs7-certificates' => 'p7b',
+ 'application/x-pkcs7-certreqresp' => 'p7r',
+ 'application/x-rar-compressed' => 'rar',
+ 'application/x-rar' => 'rar',
+ 'application/x-research-info-systems' => 'ris',
+ 'application/x-sh' => 'sh',
+ 'application/x-shar' => 'shar',
+ 'application/x-shockwave-flash' => 'swf',
+ 'application/x-silverlight-app' => 'xap',
+ 'application/x-sql' => 'sql',
+ 'application/x-stuffit' => 'sit',
+ 'application/x-stuffitx' => 'sitx',
+ 'application/x-subrip' => 'srt',
+ 'application/x-sv4cpio' => 'sv4cpio',
+ 'application/x-sv4crc' => 'sv4crc',
+ 'application/x-t3vm-image' => 't3',
+ 'application/x-tads' => 'gam',
+ 'application/x-tar' => 'tar',
+ 'application/x-tcl' => 'tcl',
+ 'application/x-tex' => 'tex',
+ 'application/x-tex-tfm' => 'tfm',
+ 'application/x-texinfo' => 'texinfo',
+ 'application/x-tgif' => 'obj',
+ 'application/x-ustar' => 'ustar',
+ 'application/x-wais-source' => 'src',
+ 'application/x-x509-ca-cert' => 'der',
+ 'application/x-xfig' => 'fig',
+ 'application/x-xliff+xml' => 'xlf',
+ 'application/x-xpinstall' => 'xpi',
+ 'application/x-xz' => 'xz',
+ 'application/x-zip-compressed' => 'zip',
+ 'application/x-zmachine' => 'z1',
+ 'application/xaml+xml' => 'xaml',
+ 'application/xcap-diff+xml' => 'xdf',
+ 'application/xenc+xml' => 'xenc',
+ 'application/xhtml+xml' => 'xhtml',
+ 'application/xml' => 'xml',
+ 'application/xml-dtd' => 'dtd',
+ 'application/xop+xml' => 'xop',
+ 'application/xproc+xml' => 'xpl',
+ 'application/xslt+xml' => 'xslt',
+ 'application/xspf+xml' => 'xspf',
+ 'application/xv+xml' => 'mxml',
+ 'application/yang' => 'yang',
+ 'application/yin+xml' => 'yin',
+ 'application/zip' => 'zip',
+ 'audio/adpcm' => 'adp',
+ 'audio/basic' => 'au',
+ 'audio/midi' => 'mid',
+ 'audio/mp4' => 'mp4a',
+ 'audio/mpeg' => 'mpga',
+ 'audio/ogg' => 'oga',
+ 'audio/s3m' => 's3m',
+ 'audio/silk' => 'sil',
+ 'audio/vnd.dece.audio' => 'uva',
+ 'audio/vnd.digital-winds' => 'eol',
+ 'audio/vnd.dra' => 'dra',
+ 'audio/vnd.dts' => 'dts',
+ 'audio/vnd.dts.hd' => 'dtshd',
+ 'audio/vnd.lucent.voice' => 'lvp',
+ 'audio/vnd.ms-playready.media.pya' => 'pya',
+ 'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
+ 'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
+ 'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
+ 'audio/vnd.rip' => 'rip',
+ 'audio/webm' => 'weba',
+ 'audio/x-aac' => 'aac',
+ 'audio/x-aiff' => 'aif',
+ 'audio/x-caf' => 'caf',
+ 'audio/x-flac' => 'flac',
+ 'audio/x-matroska' => 'mka',
+ 'audio/x-mpegurl' => 'm3u',
+ 'audio/x-ms-wax' => 'wax',
+ 'audio/x-ms-wma' => 'wma',
+ 'audio/x-pn-realaudio' => 'ram',
+ 'audio/x-pn-realaudio-plugin' => 'rmp',
+ 'audio/x-wav' => 'wav',
+ 'audio/xm' => 'xm',
+ 'chemical/x-cdx' => 'cdx',
+ 'chemical/x-cif' => 'cif',
+ 'chemical/x-cmdf' => 'cmdf',
+ 'chemical/x-cml' => 'cml',
+ 'chemical/x-csml' => 'csml',
+ 'chemical/x-xyz' => 'xyz',
+ 'image/bmp' => 'bmp',
+ 'image/x-ms-bmp' => 'bmp',
+ 'image/cgm' => 'cgm',
+ 'image/g3fax' => 'g3',
+ 'image/gif' => 'gif',
+ 'image/ief' => 'ief',
+ 'image/jpeg' => 'jpeg',
+ 'image/pjpeg' => 'jpeg',
+ 'image/ktx' => 'ktx',
+ 'image/png' => 'png',
+ 'image/prs.btif' => 'btif',
+ 'image/sgi' => 'sgi',
+ 'image/svg+xml' => 'svg',
+ 'image/tiff' => 'tiff',
+ 'image/vnd.adobe.photoshop' => 'psd',
+ 'image/vnd.dece.graphic' => 'uvi',
+ 'image/vnd.dvb.subtitle' => 'sub',
+ 'image/vnd.djvu' => 'djvu',
+ 'image/vnd.dwg' => 'dwg',
+ 'image/vnd.dxf' => 'dxf',
+ 'image/vnd.fastbidsheet' => 'fbs',
+ 'image/vnd.fpx' => 'fpx',
+ 'image/vnd.fst' => 'fst',
+ 'image/vnd.fujixerox.edmics-mmr' => 'mmr',
+ 'image/vnd.fujixerox.edmics-rlc' => 'rlc',
+ 'image/vnd.ms-modi' => 'mdi',
+ 'image/vnd.ms-photo' => 'wdp',
+ 'image/vnd.net-fpx' => 'npx',
+ 'image/vnd.wap.wbmp' => 'wbmp',
+ 'image/vnd.xiff' => 'xif',
+ 'image/webp' => 'webp',
+ 'image/x-3ds' => '3ds',
+ 'image/x-cmu-raster' => 'ras',
+ 'image/x-cmx' => 'cmx',
+ 'image/x-freehand' => 'fh',
+ 'image/x-icon' => 'ico',
+ 'image/x-mrsid-image' => 'sid',
+ 'image/x-pcx' => 'pcx',
+ 'image/x-pict' => 'pic',
+ 'image/x-portable-anymap' => 'pnm',
+ 'image/x-portable-bitmap' => 'pbm',
+ 'image/x-portable-graymap' => 'pgm',
+ 'image/x-portable-pixmap' => 'ppm',
+ 'image/x-rgb' => 'rgb',
+ 'image/x-tga' => 'tga',
+ 'image/x-xbitmap' => 'xbm',
+ 'image/x-xpixmap' => 'xpm',
+ 'image/x-xwindowdump' => 'xwd',
+ 'message/rfc822' => 'eml',
+ 'model/iges' => 'igs',
+ 'model/mesh' => 'msh',
+ 'model/vnd.collada+xml' => 'dae',
+ 'model/vnd.dwf' => 'dwf',
+ 'model/vnd.gdl' => 'gdl',
+ 'model/vnd.gtw' => 'gtw',
+ 'model/vnd.mts' => 'mts',
+ 'model/vnd.vtu' => 'vtu',
+ 'model/vrml' => 'wrl',
+ 'model/x3d+binary' => 'x3db',
+ 'model/x3d+vrml' => 'x3dv',
+ 'model/x3d+xml' => 'x3d',
+ 'text/cache-manifest' => 'appcache',
+ 'text/calendar' => 'ics',
+ 'text/css' => 'css',
+ 'text/csv' => 'csv',
+ 'text/html' => 'html',
+ 'text/n3' => 'n3',
+ 'text/plain' => 'txt',
+ 'text/prs.lines.tag' => 'dsc',
+ 'text/richtext' => 'rtx',
+ 'text/rtf' => 'rtf',
+ 'text/sgml' => 'sgml',
+ 'text/tab-separated-values' => 'tsv',
+ 'text/troff' => 't',
+ 'text/turtle' => 'ttl',
+ 'text/uri-list' => 'uri',
+ 'text/vcard' => 'vcard',
+ 'text/vnd.curl' => 'curl',
+ 'text/vnd.curl.dcurl' => 'dcurl',
+ 'text/vnd.curl.scurl' => 'scurl',
+ 'text/vnd.curl.mcurl' => 'mcurl',
+ 'text/vnd.dvb.subtitle' => 'sub',
+ 'text/vnd.fly' => 'fly',
+ 'text/vnd.fmi.flexstor' => 'flx',
+ 'text/vnd.graphviz' => 'gv',
+ 'text/vnd.in3d.3dml' => '3dml',
+ 'text/vnd.in3d.spot' => 'spot',
+ 'text/vnd.sun.j2me.app-descriptor' => 'jad',
+ 'text/vnd.wap.wml' => 'wml',
+ 'text/vnd.wap.wmlscript' => 'wmls',
+ 'text/vtt' => 'vtt',
+ 'text/x-asm' => 's',
+ 'text/x-c' => 'c',
+ 'text/x-fortran' => 'f',
+ 'text/x-pascal' => 'p',
+ 'text/x-java-source' => 'java',
+ 'text/x-opml' => 'opml',
+ 'text/x-nfo' => 'nfo',
+ 'text/x-setext' => 'etx',
+ 'text/x-sfv' => 'sfv',
+ 'text/x-uuencode' => 'uu',
+ 'text/x-vcalendar' => 'vcs',
+ 'text/x-vcard' => 'vcf',
+ 'video/3gpp' => '3gp',
+ 'video/3gpp2' => '3g2',
+ 'video/h261' => 'h261',
+ 'video/h263' => 'h263',
+ 'video/h264' => 'h264',
+ 'video/jpeg' => 'jpgv',
+ 'video/jpm' => 'jpm',
+ 'video/mj2' => 'mj2',
+ 'video/mp4' => 'mp4',
+ 'video/mpeg' => 'mpeg',
+ 'video/ogg' => 'ogv',
+ 'video/quicktime' => 'qt',
+ 'video/vnd.dece.hd' => 'uvh',
+ 'video/vnd.dece.mobile' => 'uvm',
+ 'video/vnd.dece.pd' => 'uvp',
+ 'video/vnd.dece.sd' => 'uvs',
+ 'video/vnd.dece.video' => 'uvv',
+ 'video/vnd.dvb.file' => 'dvb',
+ 'video/vnd.fvt' => 'fvt',
+ 'video/vnd.mpegurl' => 'mxu',
+ 'video/vnd.ms-playready.media.pyv' => 'pyv',
+ 'video/vnd.uvvu.mp4' => 'uvu',
+ 'video/vnd.vivo' => 'viv',
+ 'video/webm' => 'webm',
+ 'video/x-f4v' => 'f4v',
+ 'video/x-fli' => 'fli',
+ 'video/x-flv' => 'flv',
+ 'video/x-m4v' => 'm4v',
+ 'video/x-matroska' => 'mkv',
+ 'video/x-mng' => 'mng',
+ 'video/x-ms-asf' => 'asf',
+ 'video/x-ms-vob' => 'vob',
+ 'video/x-ms-wm' => 'wm',
+ 'video/x-ms-wmv' => 'wmv',
+ 'video/x-ms-wmx' => 'wmx',
+ 'video/x-ms-wvx' => 'wvx',
+ 'video/x-msvideo' => 'avi',
+ 'video/x-sgi-movie' => 'movie',
+ 'video/x-smv' => 'smv',
+ 'x-conference/x-cooltalk' => 'ice',
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($mimeType)
+ {
+ return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php
new file mode 100644
index 0000000..e3ef45e
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php
@@ -0,0 +1,142 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+
+/**
+ * A singleton mime type guesser.
+ *
+ * By default, all mime type guessers provided by the framework are installed
+ * (if available on the current OS/PHP setup).
+ *
+ * You can register custom guessers by calling the register() method on the
+ * singleton instance. Custom guessers are always called before any default ones.
+ *
+ * $guesser = MimeTypeGuesser::getInstance();
+ * $guesser->register(new MyCustomMimeTypeGuesser());
+ *
+ * If you want to change the order of the default guessers, just re-register your
+ * preferred one as a custom one. The last registered guesser is preferred over
+ * previously registered ones.
+ *
+ * Re-registering a built-in guesser also allows you to configure it:
+ *
+ * $guesser = MimeTypeGuesser::getInstance();
+ * $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file'));
+ *
+ * @author Bernhard Schussek
+ */
+class MimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ /**
+ * The singleton instance.
+ *
+ * @var MimeTypeGuesser
+ */
+ private static $instance = null;
+
+ /**
+ * All registered MimeTypeGuesserInterface instances.
+ *
+ * @var array
+ */
+ protected $guessers = array();
+
+ /**
+ * Returns the singleton instance.
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (null === self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Resets the singleton instance.
+ */
+ public static function reset()
+ {
+ self::$instance = null;
+ }
+
+ /**
+ * Registers all natively provided mime type guessers.
+ */
+ private function __construct()
+ {
+ if (FileBinaryMimeTypeGuesser::isSupported()) {
+ $this->register(new FileBinaryMimeTypeGuesser());
+ }
+
+ if (FileinfoMimeTypeGuesser::isSupported()) {
+ $this->register(new FileinfoMimeTypeGuesser());
+ }
+ }
+
+ /**
+ * Registers a new mime type guesser.
+ *
+ * When guessing, this guesser is preferred over previously registered ones.
+ */
+ public function register(MimeTypeGuesserInterface $guesser)
+ {
+ array_unshift($this->guessers, $guesser);
+ }
+
+ /**
+ * Tries to guess the mime type of the given file.
+ *
+ * The file is passed to each registered mime type guesser in reverse order
+ * of their registration (last registered is queried first). Once a guesser
+ * returns a value that is not NULL, this method terminates and returns the
+ * value.
+ *
+ * @param string $path The path to the file
+ *
+ * @return string The mime type or NULL, if none could be guessed
+ *
+ * @throws \LogicException
+ * @throws FileNotFoundException
+ * @throws AccessDeniedException
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ if (!$this->guessers) {
+ $msg = 'Unable to guess the mime type as no guessers are available';
+ if (!FileinfoMimeTypeGuesser::isSupported()) {
+ $msg .= ' (Did you enable the php_fileinfo extension?)';
+ }
+ throw new \LogicException($msg);
+ }
+
+ foreach ($this->guessers as $guesser) {
+ if (null !== $mimeType = $guesser->guess($path)) {
+ return $mimeType;
+ }
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php
new file mode 100644
index 0000000..f8c3ad2
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+
+/**
+ * Guesses the mime type of a file.
+ *
+ * @author Bernhard Schussek
+ */
+interface MimeTypeGuesserInterface
+{
+ /**
+ * Guesses the mime type of the file with the given path.
+ *
+ * @param string $path The path to the file
+ *
+ * @return string The mime type or NULL, if none could be guessed
+ *
+ * @throws FileNotFoundException If the file does not exist
+ * @throws AccessDeniedException If the file could not be read
+ */
+ public function guess($path);
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/Stream.php b/assets/php/vendor/symfony/http-foundation/File/Stream.php
new file mode 100644
index 0000000..69ae74c
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/Stream.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+/**
+ * A PHP stream of unknown size.
+ *
+ * @author Nicolas Grekas
+ */
+class Stream extends File
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ return false;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/File/UploadedFile.php b/assets/php/vendor/symfony/http-foundation/File/UploadedFile.php
new file mode 100644
index 0000000..082d8d5
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/File/UploadedFile.php
@@ -0,0 +1,266 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
+
+/**
+ * A file uploaded through a form.
+ *
+ * @author Bernhard Schussek
+ * @author Florian Eckerstorfer
+ * @author Fabien Potencier
+ */
+class UploadedFile extends File
+{
+ private $test = false;
+ private $originalName;
+ private $mimeType;
+ private $size;
+ private $error;
+
+ /**
+ * Accepts the information of the uploaded file as provided by the PHP global $_FILES.
+ *
+ * The file object is only created when the uploaded file is valid (i.e. when the
+ * isValid() method returns true). Otherwise the only methods that could be called
+ * on an UploadedFile instance are:
+ *
+ * * getClientOriginalName,
+ * * getClientMimeType,
+ * * isValid,
+ * * getError.
+ *
+ * Calling any other method on an non-valid instance will cause an unpredictable result.
+ *
+ * @param string $path The full temporary path to the file
+ * @param string $originalName The original file name of the uploaded file
+ * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream
+ * @param int|null $size The file size provided by the uploader
+ * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK
+ * @param bool $test Whether the test mode is active
+ * Local files are used in test mode hence the code should not enforce HTTP uploads
+ *
+ * @throws FileException If file_uploads is disabled
+ * @throws FileNotFoundException If the file does not exist
+ */
+ public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
+ {
+ $this->originalName = $this->getName($originalName);
+ $this->mimeType = $mimeType ?: 'application/octet-stream';
+ $this->size = $size;
+ $this->error = $error ?: UPLOAD_ERR_OK;
+ $this->test = (bool) $test;
+
+ parent::__construct($path, UPLOAD_ERR_OK === $this->error);
+ }
+
+ /**
+ * Returns the original file name.
+ *
+ * It is extracted from the request from which the file has been uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return string|null The original name
+ */
+ public function getClientOriginalName()
+ {
+ return $this->originalName;
+ }
+
+ /**
+ * Returns the original file extension.
+ *
+ * It is extracted from the original file name that was uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return string The extension
+ */
+ public function getClientOriginalExtension()
+ {
+ return pathinfo($this->originalName, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * Returns the file mime type.
+ *
+ * The client mime type is extracted from the request from which the file
+ * was uploaded, so it should not be considered as a safe value.
+ *
+ * For a trusted mime type, use getMimeType() instead (which guesses the mime
+ * type based on the file content).
+ *
+ * @return string|null The mime type
+ *
+ * @see getMimeType()
+ */
+ public function getClientMimeType()
+ {
+ return $this->mimeType;
+ }
+
+ /**
+ * Returns the extension based on the client mime type.
+ *
+ * If the mime type is unknown, returns null.
+ *
+ * This method uses the mime type as guessed by getClientMimeType()
+ * to guess the file extension. As such, the extension returned
+ * by this method cannot be trusted.
+ *
+ * For a trusted extension, use guessExtension() instead (which guesses
+ * the extension based on the guessed mime type for the file).
+ *
+ * @return string|null The guessed extension or null if it cannot be guessed
+ *
+ * @see guessExtension()
+ * @see getClientMimeType()
+ */
+ public function guessClientExtension()
+ {
+ $type = $this->getClientMimeType();
+ $guesser = ExtensionGuesser::getInstance();
+
+ return $guesser->guess($type);
+ }
+
+ /**
+ * Returns the file size.
+ *
+ * It is extracted from the request from which the file has been uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return int|null The file size
+ */
+ public function getClientSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * Returns the upload error.
+ *
+ * If the upload was successful, the constant UPLOAD_ERR_OK is returned.
+ * Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
+ *
+ * @return int The upload error
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Returns whether the file was uploaded successfully.
+ *
+ * @return bool True if the file has been uploaded with HTTP and no error occurred
+ */
+ public function isValid()
+ {
+ $isOk = UPLOAD_ERR_OK === $this->error;
+
+ return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
+ }
+
+ /**
+ * Moves the file to a new location.
+ *
+ * @param string $directory The destination folder
+ * @param string $name The new file name
+ *
+ * @return File A File object representing the new file
+ *
+ * @throws FileException if, for any reason, the file could not have been moved
+ */
+ public function move($directory, $name = null)
+ {
+ if ($this->isValid()) {
+ if ($this->test) {
+ return parent::move($directory, $name);
+ }
+
+ $target = $this->getTargetFile($directory, $name);
+
+ if (!@move_uploaded_file($this->getPathname(), $target)) {
+ $error = error_get_last();
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
+ }
+
+ @chmod($target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ throw new FileException($this->getErrorMessage());
+ }
+
+ /**
+ * Returns the maximum size of an uploaded file as configured in php.ini.
+ *
+ * @return int The maximum size of an uploaded file in bytes
+ */
+ public static function getMaxFilesize()
+ {
+ $iniMax = strtolower(ini_get('upload_max_filesize'));
+
+ if ('' === $iniMax) {
+ return PHP_INT_MAX;
+ }
+
+ $max = ltrim($iniMax, '+');
+ if (0 === strpos($max, '0x')) {
+ $max = intval($max, 16);
+ } elseif (0 === strpos($max, '0')) {
+ $max = intval($max, 8);
+ } else {
+ $max = (int) $max;
+ }
+
+ switch (substr($iniMax, -1)) {
+ case 't': $max *= 1024;
+ // no break
+ case 'g': $max *= 1024;
+ // no break
+ case 'm': $max *= 1024;
+ // no break
+ case 'k': $max *= 1024;
+ }
+
+ return $max;
+ }
+
+ /**
+ * Returns an informative upload error message.
+ *
+ * @return string The error message regarding the specified error code
+ */
+ public function getErrorMessage()
+ {
+ static $errors = array(
+ UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
+ UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
+ UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
+ UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
+ UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
+ UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
+ UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
+ );
+
+ $errorCode = $this->error;
+ $maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
+ $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.';
+
+ return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/FileBag.php b/assets/php/vendor/symfony/http-foundation/FileBag.php
new file mode 100644
index 0000000..5edd0e6
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/FileBag.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+/**
+ * FileBag is a container for uploaded files.
+ *
+ * @author Fabien Potencier
+ * @author Bulat Shakirzyanov
+ */
+class FileBag extends ParameterBag
+{
+ private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
+
+ /**
+ * @param array $parameters An array of HTTP files
+ */
+ public function __construct(array $parameters = array())
+ {
+ $this->replace($parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $files = array())
+ {
+ $this->parameters = array();
+ $this->add($files);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($key, $value)
+ {
+ if (!is_array($value) && !$value instanceof UploadedFile) {
+ throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
+ }
+
+ parent::set($key, $this->convertFileInformation($value));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add(array $files = array())
+ {
+ foreach ($files as $key => $file) {
+ $this->set($key, $file);
+ }
+ }
+
+ /**
+ * Converts uploaded files to UploadedFile instances.
+ *
+ * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information
+ *
+ * @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances
+ */
+ protected function convertFileInformation($file)
+ {
+ if ($file instanceof UploadedFile) {
+ return $file;
+ }
+
+ $file = $this->fixPhpFilesArray($file);
+ if (is_array($file)) {
+ $keys = array_keys($file);
+ sort($keys);
+
+ if ($keys == self::$fileKeys) {
+ if (UPLOAD_ERR_NO_FILE == $file['error']) {
+ $file = null;
+ } else {
+ $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']);
+ }
+ } else {
+ $file = array_map(array($this, 'convertFileInformation'), $file);
+ if (array_keys($keys) === $keys) {
+ $file = array_filter($file);
+ }
+ }
+ }
+
+ return $file;
+ }
+
+ /**
+ * Fixes a malformed PHP $_FILES array.
+ *
+ * PHP has a bug that the format of the $_FILES array differs, depending on
+ * whether the uploaded file fields had normal field names or array-like
+ * field names ("normal" vs. "parent[child]").
+ *
+ * This method fixes the array to look like the "normal" $_FILES array.
+ *
+ * It's safe to pass an already converted array, in which case this method
+ * just returns the original array unmodified.
+ *
+ * @return array
+ */
+ protected function fixPhpFilesArray($data)
+ {
+ if (!is_array($data)) {
+ return $data;
+ }
+
+ $keys = array_keys($data);
+ sort($keys);
+
+ if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) {
+ return $data;
+ }
+
+ $files = $data;
+ foreach (self::$fileKeys as $k) {
+ unset($files[$k]);
+ }
+
+ foreach ($data['name'] as $key => $name) {
+ $files[$key] = $this->fixPhpFilesArray(array(
+ 'error' => $data['error'][$key],
+ 'name' => $name,
+ 'type' => $data['type'][$key],
+ 'tmp_name' => $data['tmp_name'][$key],
+ 'size' => $data['size'][$key],
+ ));
+ }
+
+ return $files;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/HeaderBag.php b/assets/php/vendor/symfony/http-foundation/HeaderBag.php
new file mode 100644
index 0000000..7aaa52a
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/HeaderBag.php
@@ -0,0 +1,331 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * HeaderBag is a container for HTTP headers.
+ *
+ * @author Fabien Potencier
+ */
+class HeaderBag implements \IteratorAggregate, \Countable
+{
+ protected $headers = array();
+ protected $cacheControl = array();
+
+ /**
+ * @param array $headers An array of HTTP headers
+ */
+ public function __construct(array $headers = array())
+ {
+ foreach ($headers as $key => $values) {
+ $this->set($key, $values);
+ }
+ }
+
+ /**
+ * Returns the headers as a string.
+ *
+ * @return string The headers
+ */
+ public function __toString()
+ {
+ if (!$headers = $this->all()) {
+ return '';
+ }
+
+ ksort($headers);
+ $max = max(array_map('strlen', array_keys($headers))) + 1;
+ $content = '';
+ foreach ($headers as $name => $values) {
+ $name = implode('-', array_map('ucfirst', explode('-', $name)));
+ foreach ($values as $value) {
+ $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Returns the headers.
+ *
+ * @return array An array of headers
+ */
+ public function all()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * Returns the parameter keys.
+ *
+ * @return array An array of parameter keys
+ */
+ public function keys()
+ {
+ return array_keys($this->all());
+ }
+
+ /**
+ * Replaces the current HTTP headers by a new set.
+ *
+ * @param array $headers An array of HTTP headers
+ */
+ public function replace(array $headers = array())
+ {
+ $this->headers = array();
+ $this->add($headers);
+ }
+
+ /**
+ * Adds new headers the current HTTP headers set.
+ *
+ * @param array $headers An array of HTTP headers
+ */
+ public function add(array $headers)
+ {
+ foreach ($headers as $key => $values) {
+ $this->set($key, $values);
+ }
+ }
+
+ /**
+ * Returns a header value by name.
+ *
+ * @param string $key The header name
+ * @param string|string[] $default The default value
+ * @param bool $first Whether to return the first value or all header values
+ *
+ * @return string|string[] The first header value or default value if $first is true, an array of values otherwise
+ */
+ public function get($key, $default = null, $first = true)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+ $headers = $this->all();
+
+ if (!array_key_exists($key, $headers)) {
+ if (null === $default) {
+ return $first ? null : array();
+ }
+
+ return $first ? $default : array($default);
+ }
+
+ if ($first) {
+ return \count($headers[$key]) ? $headers[$key][0] : $default;
+ }
+
+ return $headers[$key];
+ }
+
+ /**
+ * Sets a header by name.
+ *
+ * @param string $key The key
+ * @param string|string[] $values The value or an array of values
+ * @param bool $replace Whether to replace the actual value or not (true by default)
+ */
+ public function set($key, $values, $replace = true)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+
+ if (\is_array($values)) {
+ $values = array_values($values);
+
+ if (true === $replace || !isset($this->headers[$key])) {
+ $this->headers[$key] = $values;
+ } else {
+ $this->headers[$key] = array_merge($this->headers[$key], $values);
+ }
+ } else {
+ if (true === $replace || !isset($this->headers[$key])) {
+ $this->headers[$key] = array($values);
+ } else {
+ $this->headers[$key][] = $values;
+ }
+ }
+
+ if ('cache-control' === $key) {
+ $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key]));
+ }
+ }
+
+ /**
+ * Returns true if the HTTP header is defined.
+ *
+ * @param string $key The HTTP header
+ *
+ * @return bool true if the parameter exists, false otherwise
+ */
+ public function has($key)
+ {
+ return array_key_exists(str_replace('_', '-', strtolower($key)), $this->all());
+ }
+
+ /**
+ * Returns true if the given HTTP header contains the given value.
+ *
+ * @param string $key The HTTP header name
+ * @param string $value The HTTP value
+ *
+ * @return bool true if the value is contained in the header, false otherwise
+ */
+ public function contains($key, $value)
+ {
+ return in_array($value, $this->get($key, null, false));
+ }
+
+ /**
+ * Removes a header.
+ *
+ * @param string $key The HTTP header name
+ */
+ public function remove($key)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+
+ unset($this->headers[$key]);
+
+ if ('cache-control' === $key) {
+ $this->cacheControl = array();
+ }
+ }
+
+ /**
+ * Returns the HTTP header value converted to a date.
+ *
+ * @param string $key The parameter key
+ * @param \DateTime $default The default value
+ *
+ * @return null|\DateTime The parsed DateTime or the default value if the header does not exist
+ *
+ * @throws \RuntimeException When the HTTP header is not parseable
+ */
+ public function getDate($key, \DateTime $default = null)
+ {
+ if (null === $value = $this->get($key)) {
+ return $default;
+ }
+
+ if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) {
+ throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value));
+ }
+
+ return $date;
+ }
+
+ /**
+ * Adds a custom Cache-Control directive.
+ *
+ * @param string $key The Cache-Control directive name
+ * @param mixed $value The Cache-Control directive value
+ */
+ public function addCacheControlDirective($key, $value = true)
+ {
+ $this->cacheControl[$key] = $value;
+
+ $this->set('Cache-Control', $this->getCacheControlHeader());
+ }
+
+ /**
+ * Returns true if the Cache-Control directive is defined.
+ *
+ * @param string $key The Cache-Control directive
+ *
+ * @return bool true if the directive exists, false otherwise
+ */
+ public function hasCacheControlDirective($key)
+ {
+ return array_key_exists($key, $this->cacheControl);
+ }
+
+ /**
+ * Returns a Cache-Control directive value by name.
+ *
+ * @param string $key The directive name
+ *
+ * @return mixed|null The directive value if defined, null otherwise
+ */
+ public function getCacheControlDirective($key)
+ {
+ return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
+ }
+
+ /**
+ * Removes a Cache-Control directive.
+ *
+ * @param string $key The Cache-Control directive
+ */
+ public function removeCacheControlDirective($key)
+ {
+ unset($this->cacheControl[$key]);
+
+ $this->set('Cache-Control', $this->getCacheControlHeader());
+ }
+
+ /**
+ * Returns an iterator for headers.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->headers);
+ }
+
+ /**
+ * Returns the number of headers.
+ *
+ * @return int The number of headers
+ */
+ public function count()
+ {
+ return count($this->headers);
+ }
+
+ protected function getCacheControlHeader()
+ {
+ $parts = array();
+ ksort($this->cacheControl);
+ foreach ($this->cacheControl as $key => $value) {
+ if (true === $value) {
+ $parts[] = $key;
+ } else {
+ if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
+ $value = '"'.$value.'"';
+ }
+
+ $parts[] = "$key=$value";
+ }
+ }
+
+ return implode(', ', $parts);
+ }
+
+ /**
+ * Parses a Cache-Control HTTP header.
+ *
+ * @param string $header The value of the Cache-Control HTTP header
+ *
+ * @return array An array representing the attribute values
+ */
+ protected function parseCacheControl($header)
+ {
+ $cacheControl = array();
+ preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true);
+ }
+
+ return $cacheControl;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/IpUtils.php b/assets/php/vendor/symfony/http-foundation/IpUtils.php
new file mode 100644
index 0000000..86d135b
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/IpUtils.php
@@ -0,0 +1,156 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Http utility functions.
+ *
+ * @author Fabien Potencier
+ */
+class IpUtils
+{
+ private static $checkedIps = array();
+
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets.
+ *
+ * @param string $requestIp IP to check
+ * @param string|array $ips List of IPs or subnets (can be a string if only a single one)
+ *
+ * @return bool Whether the IP is valid
+ */
+ public static function checkIp($requestIp, $ips)
+ {
+ if (!is_array($ips)) {
+ $ips = array($ips);
+ }
+
+ $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4';
+
+ foreach ($ips as $ip) {
+ if (self::$method($requestIp, $ip)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Compares two IPv4 addresses.
+ * In case a subnet is given, it checks if it contains the request IP.
+ *
+ * @param string $requestIp IPv4 address to check
+ * @param string $ip IPv4 address or subnet in CIDR notation
+ *
+ * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet
+ */
+ public static function checkIp4($requestIp, $ip)
+ {
+ $cacheKey = $requestIp.'-'.$ip;
+ if (isset(self::$checkedIps[$cacheKey])) {
+ return self::$checkedIps[$cacheKey];
+ }
+
+ if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ if (false !== strpos($ip, '/')) {
+ list($address, $netmask) = explode('/', $ip, 2);
+
+ if ('0' === $netmask) {
+ return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+ }
+
+ if ($netmask < 0 || $netmask > 32) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ } else {
+ $address = $ip;
+ $netmask = 32;
+ }
+
+ if (false === ip2long($address)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
+ }
+
+ /**
+ * Compares two IPv6 addresses.
+ * In case a subnet is given, it checks if it contains the request IP.
+ *
+ * @author David Soria Parra
+ *
+ * @see https://github.com/dsp/v6tools
+ *
+ * @param string $requestIp IPv6 address to check
+ * @param string $ip IPv6 address or subnet in CIDR notation
+ *
+ * @return bool Whether the IP is valid
+ *
+ * @throws \RuntimeException When IPV6 support is not enabled
+ */
+ public static function checkIp6($requestIp, $ip)
+ {
+ $cacheKey = $requestIp.'-'.$ip;
+ if (isset(self::$checkedIps[$cacheKey])) {
+ return self::$checkedIps[$cacheKey];
+ }
+
+ if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) {
+ throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
+ }
+
+ if (false !== strpos($ip, '/')) {
+ list($address, $netmask) = explode('/', $ip, 2);
+
+ if ('0' === $netmask) {
+ return (bool) unpack('n*', @inet_pton($address));
+ }
+
+ if ($netmask < 1 || $netmask > 128) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ } else {
+ $address = $ip;
+ $netmask = 128;
+ }
+
+ $bytesAddr = unpack('n*', @inet_pton($address));
+ $bytesTest = unpack('n*', @inet_pton($requestIp));
+
+ if (!$bytesAddr || !$bytesTest) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
+ $left = $netmask - 16 * ($i - 1);
+ $left = ($left <= 16) ? $left : 16;
+ $mask = ~(0xffff >> $left) & 0xffff;
+ if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ }
+
+ return self::$checkedIps[$cacheKey] = true;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/JsonResponse.php b/assets/php/vendor/symfony/http-foundation/JsonResponse.php
new file mode 100644
index 0000000..137ac33
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/JsonResponse.php
@@ -0,0 +1,220 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Response represents an HTTP response in JSON format.
+ *
+ * Note that this class does not force the returned JSON content to be an
+ * object. It is however recommended that you do return an object as it
+ * protects yourself against XSSI and JSON-JavaScript Hijacking.
+ *
+ * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside
+ *
+ * @author Igor Wiedler
+ */
+class JsonResponse extends Response
+{
+ protected $data;
+ protected $callback;
+
+ // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
+ // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
+ const DEFAULT_ENCODING_OPTIONS = 15;
+
+ protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;
+
+ /**
+ * @param mixed $data The response data
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ * @param bool $json If the data is already a JSON string
+ */
+ public function __construct($data = null, $status = 200, $headers = array(), $json = false)
+ {
+ parent::__construct('', $status, $headers);
+
+ if (null === $data) {
+ $data = new \ArrayObject();
+ }
+
+ $json ? $this->setJson($data) : $this->setData($data);
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * Example:
+ *
+ * return JsonResponse::create($data, 200)
+ * ->setSharedMaxAge(300);
+ *
+ * @param mixed $data The json response data
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($data = null, $status = 200, $headers = array())
+ {
+ return new static($data, $status, $headers);
+ }
+
+ /**
+ * Make easier the creation of JsonResponse from raw json.
+ */
+ public static function fromJsonString($data = null, $status = 200, $headers = array())
+ {
+ return new static($data, $status, $headers, true);
+ }
+
+ /**
+ * Sets the JSONP callback.
+ *
+ * @param string|null $callback The JSONP callback or null to use none
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException When the callback name is not valid
+ */
+ public function setCallback($callback = null)
+ {
+ if (null !== $callback) {
+ // partially taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/
+ // partially taken from https://github.com/willdurand/JsonpCallbackValidator
+ // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
+ // (c) William Durand
+ $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
+ $reserved = array(
+ 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
+ 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export',
+ 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
+ );
+ $parts = explode('.', $callback);
+ foreach ($parts as $part) {
+ if (!preg_match($pattern, $part) || in_array($part, $reserved, true)) {
+ throw new \InvalidArgumentException('The callback name is not valid.');
+ }
+ }
+ }
+
+ $this->callback = $callback;
+
+ return $this->update();
+ }
+
+ /**
+ * Sets a raw string containing a JSON document to be sent.
+ *
+ * @param string $json
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setJson($json)
+ {
+ $this->data = $json;
+
+ return $this->update();
+ }
+
+ /**
+ * Sets the data to be sent as JSON.
+ *
+ * @param mixed $data
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setData($data = array())
+ {
+ if (defined('HHVM_VERSION')) {
+ // HHVM does not trigger any warnings and let exceptions
+ // thrown from a JsonSerializable object pass through.
+ // If only PHP did the same...
+ $data = json_encode($data, $this->encodingOptions);
+ } else {
+ if (!interface_exists('JsonSerializable', false)) {
+ set_error_handler(function () { return false; });
+ try {
+ $data = @json_encode($data, $this->encodingOptions);
+ } finally {
+ restore_error_handler();
+ }
+ } else {
+ try {
+ $data = json_encode($data, $this->encodingOptions);
+ } catch (\Exception $e) {
+ if ('Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
+ throw $e->getPrevious() ?: $e;
+ }
+ throw $e;
+ }
+ }
+ }
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new \InvalidArgumentException(json_last_error_msg());
+ }
+
+ return $this->setJson($data);
+ }
+
+ /**
+ * Returns options used while encoding data to JSON.
+ *
+ * @return int
+ */
+ public function getEncodingOptions()
+ {
+ return $this->encodingOptions;
+ }
+
+ /**
+ * Sets options used while encoding data to JSON.
+ *
+ * @param int $encodingOptions
+ *
+ * @return $this
+ */
+ public function setEncodingOptions($encodingOptions)
+ {
+ $this->encodingOptions = (int) $encodingOptions;
+
+ return $this->setData(json_decode($this->data));
+ }
+
+ /**
+ * Updates the content and headers according to the JSON data and callback.
+ *
+ * @return $this
+ */
+ protected function update()
+ {
+ if (null !== $this->callback) {
+ // Not using application/javascript for compatibility reasons with older browsers.
+ $this->headers->set('Content-Type', 'text/javascript');
+
+ return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
+ }
+
+ // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
+ // in order to not overwrite a custom definition.
+ if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
+ $this->headers->set('Content-Type', 'application/json');
+ }
+
+ return $this->setContent($this->data);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/LICENSE b/assets/php/vendor/symfony/http-foundation/LICENSE
new file mode 100644
index 0000000..21d7fb9
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2018 Fabien Potencier
+
+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.
diff --git a/assets/php/vendor/symfony/http-foundation/ParameterBag.php b/assets/php/vendor/symfony/http-foundation/ParameterBag.php
new file mode 100644
index 0000000..257ef8b
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/ParameterBag.php
@@ -0,0 +1,234 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ParameterBag is a container for key/value pairs.
+ *
+ * @author Fabien Potencier
+ */
+class ParameterBag implements \IteratorAggregate, \Countable
+{
+ /**
+ * Parameter storage.
+ */
+ protected $parameters;
+
+ /**
+ * @param array $parameters An array of parameters
+ */
+ public function __construct(array $parameters = array())
+ {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Returns the parameters.
+ *
+ * @return array An array of parameters
+ */
+ public function all()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Returns the parameter keys.
+ *
+ * @return array An array of parameter keys
+ */
+ public function keys()
+ {
+ return array_keys($this->parameters);
+ }
+
+ /**
+ * Replaces the current parameters by a new set.
+ *
+ * @param array $parameters An array of parameters
+ */
+ public function replace(array $parameters = array())
+ {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Adds parameters.
+ *
+ * @param array $parameters An array of parameters
+ */
+ public function add(array $parameters = array())
+ {
+ $this->parameters = array_replace($this->parameters, $parameters);
+ }
+
+ /**
+ * Returns a parameter by name.
+ *
+ * @param string $key The key
+ * @param mixed $default The default value if the parameter key does not exist
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
+ }
+
+ /**
+ * Sets a parameter by name.
+ *
+ * @param string $key The key
+ * @param mixed $value The value
+ */
+ public function set($key, $value)
+ {
+ $this->parameters[$key] = $value;
+ }
+
+ /**
+ * Returns true if the parameter is defined.
+ *
+ * @param string $key The key
+ *
+ * @return bool true if the parameter exists, false otherwise
+ */
+ public function has($key)
+ {
+ return array_key_exists($key, $this->parameters);
+ }
+
+ /**
+ * Removes a parameter.
+ *
+ * @param string $key The key
+ */
+ public function remove($key)
+ {
+ unset($this->parameters[$key]);
+ }
+
+ /**
+ * Returns the alphabetic characters of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ *
+ * @return string The filtered value
+ */
+ public function getAlpha($key, $default = '')
+ {
+ return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default));
+ }
+
+ /**
+ * Returns the alphabetic characters and digits of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ *
+ * @return string The filtered value
+ */
+ public function getAlnum($key, $default = '')
+ {
+ return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default));
+ }
+
+ /**
+ * Returns the digits of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ *
+ * @return string The filtered value
+ */
+ public function getDigits($key, $default = '')
+ {
+ // we need to remove - and + because they're allowed in the filter
+ return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT));
+ }
+
+ /**
+ * Returns the parameter value converted to integer.
+ *
+ * @param string $key The parameter key
+ * @param int $default The default value if the parameter key does not exist
+ *
+ * @return int The filtered value
+ */
+ public function getInt($key, $default = 0)
+ {
+ return (int) $this->get($key, $default);
+ }
+
+ /**
+ * Returns the parameter value converted to boolean.
+ *
+ * @param string $key The parameter key
+ * @param mixed $default The default value if the parameter key does not exist
+ *
+ * @return bool The filtered value
+ */
+ public function getBoolean($key, $default = false)
+ {
+ return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN);
+ }
+
+ /**
+ * Filter key.
+ *
+ * @param string $key Key
+ * @param mixed $default Default = null
+ * @param int $filter FILTER_* constant
+ * @param mixed $options Filter options
+ *
+ * @see http://php.net/manual/en/function.filter-var.php
+ *
+ * @return mixed
+ */
+ public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array())
+ {
+ $value = $this->get($key, $default);
+
+ // Always turn $options into an array - this allows filter_var option shortcuts.
+ if (!is_array($options) && $options) {
+ $options = array('flags' => $options);
+ }
+
+ // Add a convenience check for arrays.
+ if (is_array($value) && !isset($options['flags'])) {
+ $options['flags'] = FILTER_REQUIRE_ARRAY;
+ }
+
+ return filter_var($value, $filter, $options);
+ }
+
+ /**
+ * Returns an iterator for parameters.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->parameters);
+ }
+
+ /**
+ * Returns the number of parameters.
+ *
+ * @return int The number of parameters
+ */
+ public function count()
+ {
+ return count($this->parameters);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/README.md b/assets/php/vendor/symfony/http-foundation/README.md
new file mode 100644
index 0000000..8907f0b
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/README.md
@@ -0,0 +1,14 @@
+HttpFoundation Component
+========================
+
+The HttpFoundation component defines an object-oriented layer for the HTTP
+specification.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/assets/php/vendor/symfony/http-foundation/RedirectResponse.php b/assets/php/vendor/symfony/http-foundation/RedirectResponse.php
new file mode 100644
index 0000000..01681dc
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/RedirectResponse.php
@@ -0,0 +1,109 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RedirectResponse represents an HTTP response doing a redirect.
+ *
+ * @author Fabien Potencier
+ */
+class RedirectResponse extends Response
+{
+ protected $targetUrl;
+
+ /**
+ * Creates a redirect response so that it conforms to the rules defined for a redirect status code.
+ *
+ * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc.,
+ * but practically every browser redirects on paths only as well
+ * @param int $status The status code (302 by default)
+ * @param array $headers The headers (Location is always set to the given URL)
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @see http://tools.ietf.org/html/rfc2616#section-10.3
+ */
+ public function __construct($url, $status = 302, $headers = array())
+ {
+ parent::__construct('', $status, $headers);
+
+ $this->setTargetUrl($url);
+
+ if (!$this->isRedirect()) {
+ throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
+ }
+
+ if (301 == $status && !array_key_exists('cache-control', $headers)) {
+ $this->headers->remove('cache-control');
+ }
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * @param string $url The url to redirect to
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($url = '', $status = 302, $headers = array())
+ {
+ return new static($url, $status, $headers);
+ }
+
+ /**
+ * Returns the target URL.
+ *
+ * @return string target URL
+ */
+ public function getTargetUrl()
+ {
+ return $this->targetUrl;
+ }
+
+ /**
+ * Sets the redirect target of this response.
+ *
+ * @param string $url The URL to redirect to
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setTargetUrl($url)
+ {
+ if (empty($url)) {
+ throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
+ }
+
+ $this->targetUrl = $url;
+
+ $this->setContent(
+ sprintf('
+
+
+
+
+
+ Redirecting to %1$s
+
+
+ Redirecting to %1$s.
+
+', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')));
+
+ $this->headers->set('Location', $url);
+
+ return $this;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Request.php b/assets/php/vendor/symfony/http-foundation/Request.php
new file mode 100644
index 0000000..164fb4e
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Request.php
@@ -0,0 +1,2154 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
+use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+/**
+ * Request represents an HTTP request.
+ *
+ * The methods dealing with URL accept / return a raw path (% encoded):
+ * * getBasePath
+ * * getBaseUrl
+ * * getPathInfo
+ * * getRequestUri
+ * * getUri
+ * * getUriForPath
+ *
+ * @author Fabien Potencier
+ */
+class Request
+{
+ const HEADER_FORWARDED = 0b00001; // When using RFC 7239
+ const HEADER_X_FORWARDED_FOR = 0b00010;
+ const HEADER_X_FORWARDED_HOST = 0b00100;
+ const HEADER_X_FORWARDED_PROTO = 0b01000;
+ const HEADER_X_FORWARDED_PORT = 0b10000;
+ const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
+ const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
+
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT;
+
+ const METHOD_HEAD = 'HEAD';
+ const METHOD_GET = 'GET';
+ const METHOD_POST = 'POST';
+ const METHOD_PUT = 'PUT';
+ const METHOD_PATCH = 'PATCH';
+ const METHOD_DELETE = 'DELETE';
+ const METHOD_PURGE = 'PURGE';
+ const METHOD_OPTIONS = 'OPTIONS';
+ const METHOD_TRACE = 'TRACE';
+ const METHOD_CONNECT = 'CONNECT';
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedProxies = array();
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedHostPatterns = array();
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedHosts = array();
+
+ /**
+ * Names for headers that can be trusted when
+ * using trusted proxies.
+ *
+ * The FORWARDED header is the standard as of rfc7239.
+ *
+ * The other headers are non-standard, but widely used
+ * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
+ *
+ * @deprecated since version 3.3, to be removed in 4.0
+ */
+ protected static $trustedHeaders = array(
+ self::HEADER_FORWARDED => 'FORWARDED',
+ self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
+ self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
+ self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
+ self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
+ );
+
+ protected static $httpMethodParameterOverride = false;
+
+ /**
+ * Custom parameters.
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $attributes;
+
+ /**
+ * Request body parameters ($_POST).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $request;
+
+ /**
+ * Query string parameters ($_GET).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $query;
+
+ /**
+ * Server and execution environment parameters ($_SERVER).
+ *
+ * @var \Symfony\Component\HttpFoundation\ServerBag
+ */
+ public $server;
+
+ /**
+ * Uploaded files ($_FILES).
+ *
+ * @var \Symfony\Component\HttpFoundation\FileBag
+ */
+ public $files;
+
+ /**
+ * Cookies ($_COOKIE).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $cookies;
+
+ /**
+ * Headers (taken from the $_SERVER).
+ *
+ * @var \Symfony\Component\HttpFoundation\HeaderBag
+ */
+ public $headers;
+
+ /**
+ * @var string|resource|false|null
+ */
+ protected $content;
+
+ /**
+ * @var array
+ */
+ protected $languages;
+
+ /**
+ * @var array
+ */
+ protected $charsets;
+
+ /**
+ * @var array
+ */
+ protected $encodings;
+
+ /**
+ * @var array
+ */
+ protected $acceptableContentTypes;
+
+ /**
+ * @var string
+ */
+ protected $pathInfo;
+
+ /**
+ * @var string
+ */
+ protected $requestUri;
+
+ /**
+ * @var string
+ */
+ protected $baseUrl;
+
+ /**
+ * @var string
+ */
+ protected $basePath;
+
+ /**
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * @var string
+ */
+ protected $format;
+
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+ */
+ protected $session;
+
+ /**
+ * @var string
+ */
+ protected $locale;
+
+ /**
+ * @var string
+ */
+ protected $defaultLocale = 'en';
+
+ /**
+ * @var array
+ */
+ protected static $formats;
+
+ protected static $requestFactory;
+
+ private $isHostValid = true;
+ private $isForwardedValid = true;
+
+ private static $trustedHeaderSet = -1;
+
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ private static $trustedHeaderNames = array(
+ self::HEADER_FORWARDED => 'FORWARDED',
+ self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
+ self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
+ self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
+ self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
+ );
+
+ private static $forwardedParams = array(
+ self::HEADER_X_FORWARDED_FOR => 'for',
+ self::HEADER_X_FORWARDED_HOST => 'host',
+ self::HEADER_X_FORWARDED_PROTO => 'proto',
+ self::HEADER_X_FORWARDED_PORT => 'host',
+ );
+
+ /**
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
+ */
+ public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+ {
+ $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Sets the parameters for this request.
+ *
+ * This method also re-initializes all properties.
+ *
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
+ */
+ public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+ {
+ $this->request = new ParameterBag($request);
+ $this->query = new ParameterBag($query);
+ $this->attributes = new ParameterBag($attributes);
+ $this->cookies = new ParameterBag($cookies);
+ $this->files = new FileBag($files);
+ $this->server = new ServerBag($server);
+ $this->headers = new HeaderBag($this->server->getHeaders());
+
+ $this->content = $content;
+ $this->languages = null;
+ $this->charsets = null;
+ $this->encodings = null;
+ $this->acceptableContentTypes = null;
+ $this->pathInfo = null;
+ $this->requestUri = null;
+ $this->baseUrl = null;
+ $this->basePath = null;
+ $this->method = null;
+ $this->format = null;
+ }
+
+ /**
+ * Creates a new request with values from PHP's super globals.
+ *
+ * @return static
+ */
+ public static function createFromGlobals()
+ {
+ // With the php's bug #66606, the php's built-in web server
+ // stores the Content-Type and Content-Length header values in
+ // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
+ $server = $_SERVER;
+ if ('cli-server' === PHP_SAPI) {
+ if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
+ $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
+ }
+ if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
+ $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
+ }
+ }
+
+ $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);
+
+ if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
+ && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
+ ) {
+ parse_str($request->getContent(), $data);
+ $request->request = new ParameterBag($data);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Creates a Request based on a given URI and configuration.
+ *
+ * The information contained in the URI always take precedence
+ * over the other information (server and parameters).
+ *
+ * @param string $uri The URI
+ * @param string $method The HTTP method
+ * @param array $parameters The query (GET) or request (POST) parameters
+ * @param array $cookies The request cookies ($_COOKIE)
+ * @param array $files The request files ($_FILES)
+ * @param array $server The server parameters ($_SERVER)
+ * @param string|resource|null $content The raw body data
+ *
+ * @return static
+ */
+ public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
+ {
+ $server = array_replace(array(
+ 'SERVER_NAME' => 'localhost',
+ 'SERVER_PORT' => 80,
+ 'HTTP_HOST' => 'localhost',
+ 'HTTP_USER_AGENT' => 'Symfony/3.X',
+ 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
+ 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '',
+ 'SCRIPT_FILENAME' => '',
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
+ 'REQUEST_TIME' => time(),
+ ), $server);
+
+ $server['PATH_INFO'] = '';
+ $server['REQUEST_METHOD'] = strtoupper($method);
+
+ $components = parse_url($uri);
+ if (isset($components['host'])) {
+ $server['SERVER_NAME'] = $components['host'];
+ $server['HTTP_HOST'] = $components['host'];
+ }
+
+ if (isset($components['scheme'])) {
+ if ('https' === $components['scheme']) {
+ $server['HTTPS'] = 'on';
+ $server['SERVER_PORT'] = 443;
+ } else {
+ unset($server['HTTPS']);
+ $server['SERVER_PORT'] = 80;
+ }
+ }
+
+ if (isset($components['port'])) {
+ $server['SERVER_PORT'] = $components['port'];
+ $server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port'];
+ }
+
+ if (isset($components['user'])) {
+ $server['PHP_AUTH_USER'] = $components['user'];
+ }
+
+ if (isset($components['pass'])) {
+ $server['PHP_AUTH_PW'] = $components['pass'];
+ }
+
+ if (!isset($components['path'])) {
+ $components['path'] = '/';
+ }
+
+ switch (strtoupper($method)) {
+ case 'POST':
+ case 'PUT':
+ case 'DELETE':
+ if (!isset($server['CONTENT_TYPE'])) {
+ $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ }
+ // no break
+ case 'PATCH':
+ $request = $parameters;
+ $query = array();
+ break;
+ default:
+ $request = array();
+ $query = $parameters;
+ break;
+ }
+
+ $queryString = '';
+ if (isset($components['query'])) {
+ parse_str(html_entity_decode($components['query']), $qs);
+
+ if ($query) {
+ $query = array_replace($qs, $query);
+ $queryString = http_build_query($query, '', '&');
+ } else {
+ $query = $qs;
+ $queryString = $components['query'];
+ }
+ } elseif ($query) {
+ $queryString = http_build_query($query, '', '&');
+ }
+
+ $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
+ $server['QUERY_STRING'] = $queryString;
+
+ return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Sets a callable able to create a Request instance.
+ *
+ * This is mainly useful when you need to override the Request class
+ * to keep BC with an existing system. It should not be used for any
+ * other purpose.
+ *
+ * @param callable|null $callable A PHP callable
+ */
+ public static function setFactory($callable)
+ {
+ self::$requestFactory = $callable;
+ }
+
+ /**
+ * Clones a request and overrides some of its parameters.
+ *
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ *
+ * @return static
+ */
+ public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
+ {
+ $dup = clone $this;
+ if (null !== $query) {
+ $dup->query = new ParameterBag($query);
+ }
+ if (null !== $request) {
+ $dup->request = new ParameterBag($request);
+ }
+ if (null !== $attributes) {
+ $dup->attributes = new ParameterBag($attributes);
+ }
+ if (null !== $cookies) {
+ $dup->cookies = new ParameterBag($cookies);
+ }
+ if (null !== $files) {
+ $dup->files = new FileBag($files);
+ }
+ if (null !== $server) {
+ $dup->server = new ServerBag($server);
+ $dup->headers = new HeaderBag($dup->server->getHeaders());
+ }
+ $dup->languages = null;
+ $dup->charsets = null;
+ $dup->encodings = null;
+ $dup->acceptableContentTypes = null;
+ $dup->pathInfo = null;
+ $dup->requestUri = null;
+ $dup->baseUrl = null;
+ $dup->basePath = null;
+ $dup->method = null;
+ $dup->format = null;
+
+ if (!$dup->get('_format') && $this->get('_format')) {
+ $dup->attributes->set('_format', $this->get('_format'));
+ }
+
+ if (!$dup->getRequestFormat(null)) {
+ $dup->setRequestFormat($this->getRequestFormat(null));
+ }
+
+ return $dup;
+ }
+
+ /**
+ * Clones the current request.
+ *
+ * Note that the session is not cloned as duplicated requests
+ * are most of the time sub-requests of the main one.
+ */
+ public function __clone()
+ {
+ $this->query = clone $this->query;
+ $this->request = clone $this->request;
+ $this->attributes = clone $this->attributes;
+ $this->cookies = clone $this->cookies;
+ $this->files = clone $this->files;
+ $this->server = clone $this->server;
+ $this->headers = clone $this->headers;
+ }
+
+ /**
+ * Returns the request as a string.
+ *
+ * @return string The request
+ */
+ public function __toString()
+ {
+ try {
+ $content = $this->getContent();
+ } catch (\LogicException $e) {
+ return trigger_error($e, E_USER_ERROR);
+ }
+
+ $cookieHeader = '';
+ $cookies = array();
+
+ foreach ($this->cookies as $k => $v) {
+ $cookies[] = $k.'='.$v;
+ }
+
+ if (!empty($cookies)) {
+ $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
+ }
+
+ return
+ sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
+ $this->headers.
+ $cookieHeader."\r\n".
+ $content;
+ }
+
+ /**
+ * Overrides the PHP global variables according to this request instance.
+ *
+ * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE.
+ * $_FILES is never overridden, see rfc1867
+ */
+ public function overrideGlobals()
+ {
+ $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&')));
+
+ $_GET = $this->query->all();
+ $_POST = $this->request->all();
+ $_SERVER = $this->server->all();
+ $_COOKIE = $this->cookies->all();
+
+ foreach ($this->headers->all() as $key => $value) {
+ $key = strtoupper(str_replace('-', '_', $key));
+ if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
+ $_SERVER[$key] = implode(', ', $value);
+ } else {
+ $_SERVER['HTTP_'.$key] = implode(', ', $value);
+ }
+ }
+
+ $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE);
+
+ $requestOrder = ini_get('request_order') ?: ini_get('variables_order');
+ $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
+
+ $_REQUEST = array();
+ foreach (str_split($requestOrder) as $order) {
+ $_REQUEST = array_merge($_REQUEST, $request[$order]);
+ }
+ }
+
+ /**
+ * Sets a list of trusted proxies.
+ *
+ * You should only list the reverse proxies that you manage directly.
+ *
+ * @param array $proxies A list of trusted proxies
+ * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies
+ *
+ * @throws \InvalidArgumentException When $trustedHeaderSet is invalid
+ */
+ public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/)
+ {
+ self::$trustedProxies = $proxies;
+
+ if (2 > func_num_args()) {
+ @trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since Symfony 3.3. Defining it will be required in 4.0. ', __METHOD__), E_USER_DEPRECATED);
+
+ return;
+ }
+ $trustedHeaderSet = (int) func_get_arg(1);
+
+ foreach (self::$trustedHeaderNames as $header => $name) {
+ self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null;
+ }
+ self::$trustedHeaderSet = $trustedHeaderSet;
+ }
+
+ /**
+ * Gets the list of trusted proxies.
+ *
+ * @return array An array of trusted proxies
+ */
+ public static function getTrustedProxies()
+ {
+ return self::$trustedProxies;
+ }
+
+ /**
+ * Gets the set of trusted headers from trusted proxies.
+ *
+ * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies
+ */
+ public static function getTrustedHeaderSet()
+ {
+ return self::$trustedHeaderSet;
+ }
+
+ /**
+ * Sets a list of trusted host patterns.
+ *
+ * You should only list the hosts you manage using regexs.
+ *
+ * @param array $hostPatterns A list of trusted host patterns
+ */
+ public static function setTrustedHosts(array $hostPatterns)
+ {
+ self::$trustedHostPatterns = array_map(function ($hostPattern) {
+ return sprintf('#%s#i', $hostPattern);
+ }, $hostPatterns);
+ // we need to reset trusted hosts on trusted host patterns change
+ self::$trustedHosts = array();
+ }
+
+ /**
+ * Gets the list of trusted host patterns.
+ *
+ * @return array An array of trusted host patterns
+ */
+ public static function getTrustedHosts()
+ {
+ return self::$trustedHostPatterns;
+ }
+
+ /**
+ * Sets the name for trusted headers.
+ *
+ * The following header keys are supported:
+ *
+ * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp())
+ * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost())
+ * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort())
+ * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
+ * * Request::HEADER_FORWARDED: defaults to Forwarded (see RFC 7239)
+ *
+ * Setting an empty value allows to disable the trusted header for the given key.
+ *
+ * @param string $key The header key
+ * @param string $value The header name
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.
+ */
+ public static function setTrustedHeaderName($key, $value)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), E_USER_DEPRECATED);
+
+ if ('forwarded' === $key) {
+ $key = self::HEADER_FORWARDED;
+ } elseif ('client_ip' === $key) {
+ $key = self::HEADER_CLIENT_IP;
+ } elseif ('client_host' === $key) {
+ $key = self::HEADER_CLIENT_HOST;
+ } elseif ('client_proto' === $key) {
+ $key = self::HEADER_CLIENT_PROTO;
+ } elseif ('client_port' === $key) {
+ $key = self::HEADER_CLIENT_PORT;
+ } elseif (!array_key_exists($key, self::$trustedHeaders)) {
+ throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
+ }
+
+ self::$trustedHeaders[$key] = $value;
+
+ if (null !== $value) {
+ self::$trustedHeaderNames[$key] = $value;
+ self::$trustedHeaderSet |= $key;
+ } else {
+ self::$trustedHeaderSet &= ~$key;
+ }
+ }
+
+ /**
+ * Gets the trusted proxy header name.
+ *
+ * @param string $key The header key
+ *
+ * @return string The header name
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.
+ */
+ public static function getTrustedHeaderName($key)
+ {
+ if (2 > func_num_args() || func_get_arg(1)) {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), E_USER_DEPRECATED);
+ }
+
+ if (!array_key_exists($key, self::$trustedHeaders)) {
+ throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key));
+ }
+
+ return self::$trustedHeaders[$key];
+ }
+
+ /**
+ * Normalizes a query string.
+ *
+ * It builds a normalized query string, where keys/value pairs are alphabetized,
+ * have consistent escaping and unneeded delimiters are removed.
+ *
+ * @param string $qs Query string
+ *
+ * @return string A normalized query string for the Request
+ */
+ public static function normalizeQueryString($qs)
+ {
+ if ('' == $qs) {
+ return '';
+ }
+
+ $parts = array();
+ $order = array();
+
+ foreach (explode('&', $qs) as $param) {
+ if ('' === $param || '=' === $param[0]) {
+ // Ignore useless delimiters, e.g. "x=y&".
+ // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
+ // PHP also does not include them when building _GET.
+ continue;
+ }
+
+ $keyValuePair = explode('=', $param, 2);
+
+ // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
+ // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to
+ // RFC 3986 with rawurlencode.
+ $parts[] = isset($keyValuePair[1]) ?
+ rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
+ rawurlencode(urldecode($keyValuePair[0]));
+ $order[] = urldecode($keyValuePair[0]);
+ }
+
+ array_multisort($order, SORT_ASC, $parts);
+
+ return implode('&', $parts);
+ }
+
+ /**
+ * Enables support for the _method request parameter to determine the intended HTTP method.
+ *
+ * Be warned that enabling this feature might lead to CSRF issues in your code.
+ * Check that you are using CSRF tokens when required.
+ * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
+ * and used to send a "PUT" or "DELETE" request via the _method request parameter.
+ * If these methods are not protected against CSRF, this presents a possible vulnerability.
+ *
+ * The HTTP method can only be overridden when the real HTTP method is POST.
+ */
+ public static function enableHttpMethodParameterOverride()
+ {
+ self::$httpMethodParameterOverride = true;
+ }
+
+ /**
+ * Checks whether support for the _method request parameter is enabled.
+ *
+ * @return bool True when the _method request parameter is enabled, false otherwise
+ */
+ public static function getHttpMethodParameterOverride()
+ {
+ return self::$httpMethodParameterOverride;
+ }
+
+ /**
+ * Gets a "parameter" value from any bag.
+ *
+ * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
+ * flexibility in controllers, it is better to explicitly get request parameters from the appropriate
+ * public property instead (attributes, query, request).
+ *
+ * Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY
+ *
+ * @param string $key The key
+ * @param mixed $default The default value if the parameter key does not exist
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if ($this !== $result = $this->attributes->get($key, $this)) {
+ return $result;
+ }
+
+ if ($this !== $result = $this->query->get($key, $this)) {
+ return $result;
+ }
+
+ if ($this !== $result = $this->request->get($key, $this)) {
+ return $result;
+ }
+
+ return $default;
+ }
+
+ /**
+ * Gets the Session.
+ *
+ * @return SessionInterface|null The session
+ */
+ public function getSession()
+ {
+ return $this->session;
+ }
+
+ /**
+ * Whether the request contains a Session which was started in one of the
+ * previous requests.
+ *
+ * @return bool
+ */
+ public function hasPreviousSession()
+ {
+ // the check for $this->session avoids malicious users trying to fake a session cookie with proper name
+ return $this->hasSession() && $this->cookies->has($this->session->getName());
+ }
+
+ /**
+ * Whether the request contains a Session object.
+ *
+ * This method does not give any information about the state of the session object,
+ * like whether the session is started or not. It is just a way to check if this Request
+ * is associated with a Session instance.
+ *
+ * @return bool true when the Request contains a Session object, false otherwise
+ */
+ public function hasSession()
+ {
+ return null !== $this->session;
+ }
+
+ /**
+ * Sets the Session.
+ *
+ * @param SessionInterface $session The Session
+ */
+ public function setSession(SessionInterface $session)
+ {
+ $this->session = $session;
+ }
+
+ /**
+ * Returns the client IP addresses.
+ *
+ * In the returned array the most trusted IP address is first, and the
+ * least trusted one last. The "real" client IP address is the last one,
+ * but this is also the least trusted one. Trusted proxies are stripped.
+ *
+ * Use this method carefully; you should use getClientIp() instead.
+ *
+ * @return array The client IP addresses
+ *
+ * @see getClientIp()
+ */
+ public function getClientIps()
+ {
+ $ip = $this->server->get('REMOTE_ADDR');
+
+ if (!$this->isFromTrustedProxy()) {
+ return array($ip);
+ }
+
+ return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip);
+ }
+
+ /**
+ * Returns the client IP address.
+ *
+ * This method can read the client IP address from the "X-Forwarded-For" header
+ * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
+ * header value is a comma+space separated list of IP addresses, the left-most
+ * being the original client, and each successive proxy that passed the request
+ * adding the IP address where it received the request from.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-For",
+ * ("Client-Ip" for instance), configure it via the $trustedHeaderSet
+ * argument of the Request::setTrustedProxies() method instead.
+ *
+ * @return string|null The client IP address
+ *
+ * @see getClientIps()
+ * @see http://en.wikipedia.org/wiki/X-Forwarded-For
+ */
+ public function getClientIp()
+ {
+ $ipAddresses = $this->getClientIps();
+
+ return $ipAddresses[0];
+ }
+
+ /**
+ * Returns current script name.
+ *
+ * @return string
+ */
+ public function getScriptName()
+ {
+ return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
+ }
+
+ /**
+ * Returns the path being requested relative to the executed script.
+ *
+ * The path info always starts with a /.
+ *
+ * Suppose this request is instantiated from /mysite on localhost:
+ *
+ * * http://localhost/mysite returns an empty string
+ * * http://localhost/mysite/about returns '/about'
+ * * http://localhost/mysite/enco%20ded returns '/enco%20ded'
+ * * http://localhost/mysite/about?var=1 returns '/about'
+ *
+ * @return string The raw path (i.e. not urldecoded)
+ */
+ public function getPathInfo()
+ {
+ if (null === $this->pathInfo) {
+ $this->pathInfo = $this->preparePathInfo();
+ }
+
+ return $this->pathInfo;
+ }
+
+ /**
+ * Returns the root path from which this request is executed.
+ *
+ * Suppose that an index.php file instantiates this request object:
+ *
+ * * http://localhost/index.php returns an empty string
+ * * http://localhost/index.php/page returns an empty string
+ * * http://localhost/web/index.php returns '/web'
+ * * http://localhost/we%20b/index.php returns '/we%20b'
+ *
+ * @return string The raw path (i.e. not urldecoded)
+ */
+ public function getBasePath()
+ {
+ if (null === $this->basePath) {
+ $this->basePath = $this->prepareBasePath();
+ }
+
+ return $this->basePath;
+ }
+
+ /**
+ * Returns the root URL from which this request is executed.
+ *
+ * The base URL never ends with a /.
+ *
+ * This is similar to getBasePath(), except that it also includes the
+ * script filename (e.g. index.php) if one exists.
+ *
+ * @return string The raw URL (i.e. not urldecoded)
+ */
+ public function getBaseUrl()
+ {
+ if (null === $this->baseUrl) {
+ $this->baseUrl = $this->prepareBaseUrl();
+ }
+
+ return $this->baseUrl;
+ }
+
+ /**
+ * Gets the request's scheme.
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ return $this->isSecure() ? 'https' : 'http';
+ }
+
+ /**
+ * Returns the port on which the request is made.
+ *
+ * This method can read the client port from the "X-Forwarded-Port" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Port" header must contain the client port.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Port",
+ * configure it via via the $trustedHeaderSet argument of the
+ * Request::setTrustedProxies() method instead.
+ *
+ * @return int|string can be a string if fetched from the server bag
+ */
+ public function getPort()
+ {
+ if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) {
+ $host = $host[0];
+ } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
+ $host = $host[0];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ return $this->server->get('SERVER_PORT');
+ }
+
+ if ('[' === $host[0]) {
+ $pos = strpos($host, ':', strrpos($host, ']'));
+ } else {
+ $pos = strrpos($host, ':');
+ }
+
+ if (false !== $pos) {
+ return (int) substr($host, $pos + 1);
+ }
+
+ return 'https' === $this->getScheme() ? 443 : 80;
+ }
+
+ /**
+ * Returns the user.
+ *
+ * @return string|null
+ */
+ public function getUser()
+ {
+ return $this->headers->get('PHP_AUTH_USER');
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return string|null
+ */
+ public function getPassword()
+ {
+ return $this->headers->get('PHP_AUTH_PW');
+ }
+
+ /**
+ * Gets the user info.
+ *
+ * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server
+ */
+ public function getUserInfo()
+ {
+ $userinfo = $this->getUser();
+
+ $pass = $this->getPassword();
+ if ('' != $pass) {
+ $userinfo .= ":$pass";
+ }
+
+ return $userinfo;
+ }
+
+ /**
+ * Returns the HTTP host being requested.
+ *
+ * The port name will be appended to the host if it's non-standard.
+ *
+ * @return string
+ */
+ public function getHttpHost()
+ {
+ $scheme = $this->getScheme();
+ $port = $this->getPort();
+
+ if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) {
+ return $this->getHost();
+ }
+
+ return $this->getHost().':'.$port;
+ }
+
+ /**
+ * Returns the requested URI (path and query string).
+ *
+ * @return string The raw URI (i.e. not URI decoded)
+ */
+ public function getRequestUri()
+ {
+ if (null === $this->requestUri) {
+ $this->requestUri = $this->prepareRequestUri();
+ }
+
+ return $this->requestUri;
+ }
+
+ /**
+ * Gets the scheme and HTTP host.
+ *
+ * If the URL was called with basic authentication, the user
+ * and the password are not added to the generated string.
+ *
+ * @return string The scheme and HTTP host
+ */
+ public function getSchemeAndHttpHost()
+ {
+ return $this->getScheme().'://'.$this->getHttpHost();
+ }
+
+ /**
+ * Generates a normalized URI (URL) for the Request.
+ *
+ * @return string A normalized URI (URL) for the Request
+ *
+ * @see getQueryString()
+ */
+ public function getUri()
+ {
+ if (null !== $qs = $this->getQueryString()) {
+ $qs = '?'.$qs;
+ }
+
+ return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
+ }
+
+ /**
+ * Generates a normalized URI for the given path.
+ *
+ * @param string $path A path to use instead of the current one
+ *
+ * @return string The normalized URI for the path
+ */
+ public function getUriForPath($path)
+ {
+ return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
+ }
+
+ /**
+ * Returns the path as relative reference from the current Request path.
+ *
+ * Only the URIs path component (no schema, host etc.) is relevant and must be given.
+ * Both paths must be absolute and not contain relative parts.
+ * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
+ * Furthermore, they can be used to reduce the link size in documents.
+ *
+ * Example target paths, given a base path of "/a/b/c/d":
+ * - "/a/b/c/d" -> ""
+ * - "/a/b/c/" -> "./"
+ * - "/a/b/" -> "../"
+ * - "/a/b/c/other" -> "other"
+ * - "/a/x/y" -> "../../x/y"
+ *
+ * @param string $path The target path
+ *
+ * @return string The relative target path
+ */
+ public function getRelativeUriForPath($path)
+ {
+ // be sure that we are dealing with an absolute path
+ if (!isset($path[0]) || '/' !== $path[0]) {
+ return $path;
+ }
+
+ if ($path === $basePath = $this->getPathInfo()) {
+ return '';
+ }
+
+ $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
+ $targetDirs = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path);
+ array_pop($sourceDirs);
+ $targetFile = array_pop($targetDirs);
+
+ foreach ($sourceDirs as $i => $dir) {
+ if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
+ unset($sourceDirs[$i], $targetDirs[$i]);
+ } else {
+ break;
+ }
+ }
+
+ $targetDirs[] = $targetFile;
+ $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs);
+
+ // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
+ // (see http://tools.ietf.org/html/rfc3986#section-4.2).
+ return !isset($path[0]) || '/' === $path[0]
+ || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
+ ? "./$path" : $path;
+ }
+
+ /**
+ * Generates the normalized query string for the Request.
+ *
+ * It builds a normalized query string, where keys/value pairs are alphabetized
+ * and have consistent escaping.
+ *
+ * @return string|null A normalized query string for the Request
+ */
+ public function getQueryString()
+ {
+ $qs = static::normalizeQueryString($this->server->get('QUERY_STRING'));
+
+ return '' === $qs ? null : $qs;
+ }
+
+ /**
+ * Checks whether the request is secure or not.
+ *
+ * This method can read the client protocol from the "X-Forwarded-Proto" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Proto"
+ * ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet
+ * argument of the Request::setTrustedProxies() method instead.
+ *
+ * @return bool
+ */
+ public function isSecure()
+ {
+ if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
+ return in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true);
+ }
+
+ $https = $this->server->get('HTTPS');
+
+ return !empty($https) && 'off' !== strtolower($https);
+ }
+
+ /**
+ * Returns the host name.
+ *
+ * This method can read the client host name from the "X-Forwarded-Host" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Host" header must contain the client host name.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Host",
+ * configure it via the $trustedHeaderSet argument of the
+ * Request::setTrustedProxies() method instead.
+ *
+ * @return string
+ *
+ * @throws SuspiciousOperationException when the host name is invalid or not trusted
+ */
+ public function getHost()
+ {
+ if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
+ $host = $host[0];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ if (!$host = $this->server->get('SERVER_NAME')) {
+ $host = $this->server->get('SERVER_ADDR', '');
+ }
+ }
+
+ // trim and remove port number from host
+ // host is lowercase as per RFC 952/2181
+ $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
+
+ // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
+ // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
+ // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
+ if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
+ if (!$this->isHostValid) {
+ return '';
+ }
+ $this->isHostValid = false;
+
+ throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host));
+ }
+
+ if (count(self::$trustedHostPatterns) > 0) {
+ // to avoid host header injection attacks, you should provide a list of trusted host patterns
+
+ if (in_array($host, self::$trustedHosts)) {
+ return $host;
+ }
+
+ foreach (self::$trustedHostPatterns as $pattern) {
+ if (preg_match($pattern, $host)) {
+ self::$trustedHosts[] = $host;
+
+ return $host;
+ }
+ }
+
+ if (!$this->isHostValid) {
+ return '';
+ }
+ $this->isHostValid = false;
+
+ throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host));
+ }
+
+ return $host;
+ }
+
+ /**
+ * Sets the request method.
+ *
+ * @param string $method
+ */
+ public function setMethod($method)
+ {
+ $this->method = null;
+ $this->server->set('REQUEST_METHOD', $method);
+ }
+
+ /**
+ * Gets the request "intended" method.
+ *
+ * If the X-HTTP-Method-Override header is set, and if the method is a POST,
+ * then it is used to determine the "real" intended HTTP method.
+ *
+ * The _method request parameter can also be used to determine the HTTP method,
+ * but only if enableHttpMethodParameterOverride() has been called.
+ *
+ * The method is always an uppercased string.
+ *
+ * @return string The request method
+ *
+ * @see getRealMethod()
+ */
+ public function getMethod()
+ {
+ if (null === $this->method) {
+ $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+
+ if ('POST' === $this->method) {
+ if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
+ $this->method = strtoupper($method);
+ } elseif (self::$httpMethodParameterOverride) {
+ $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
+ }
+ }
+ }
+
+ return $this->method;
+ }
+
+ /**
+ * Gets the "real" request method.
+ *
+ * @return string The request method
+ *
+ * @see getMethod()
+ */
+ public function getRealMethod()
+ {
+ return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+ }
+
+ /**
+ * Gets the mime type associated with the format.
+ *
+ * @param string $format The format
+ *
+ * @return string The associated mime type (null if not found)
+ */
+ public function getMimeType($format)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
+ }
+
+ /**
+ * Gets the mime types associated with the format.
+ *
+ * @param string $format The format
+ *
+ * @return array The associated mime types
+ */
+ public static function getMimeTypes($format)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ return isset(static::$formats[$format]) ? static::$formats[$format] : array();
+ }
+
+ /**
+ * Gets the format associated with the mime type.
+ *
+ * @param string $mimeType The associated mime type
+ *
+ * @return string|null The format (null if not found)
+ */
+ public function getFormat($mimeType)
+ {
+ $canonicalMimeType = null;
+ if (false !== $pos = strpos($mimeType, ';')) {
+ $canonicalMimeType = substr($mimeType, 0, $pos);
+ }
+
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ foreach (static::$formats as $format => $mimeTypes) {
+ if (in_array($mimeType, (array) $mimeTypes)) {
+ return $format;
+ }
+ if (null !== $canonicalMimeType && in_array($canonicalMimeType, (array) $mimeTypes)) {
+ return $format;
+ }
+ }
+ }
+
+ /**
+ * Associates a format with mime types.
+ *
+ * @param string $format The format
+ * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
+ */
+ public function setFormat($format, $mimeTypes)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes);
+ }
+
+ /**
+ * Gets the request format.
+ *
+ * Here is the process to determine the format:
+ *
+ * * format defined by the user (with setRequestFormat())
+ * * _format request attribute
+ * * $default
+ *
+ * @param string $default The default format
+ *
+ * @return string The request format
+ */
+ public function getRequestFormat($default = 'html')
+ {
+ if (null === $this->format) {
+ $this->format = $this->attributes->get('_format');
+ }
+
+ return null === $this->format ? $default : $this->format;
+ }
+
+ /**
+ * Sets the request format.
+ *
+ * @param string $format The request format
+ */
+ public function setRequestFormat($format)
+ {
+ $this->format = $format;
+ }
+
+ /**
+ * Gets the format associated with the request.
+ *
+ * @return string|null The format (null if no content type is present)
+ */
+ public function getContentType()
+ {
+ return $this->getFormat($this->headers->get('CONTENT_TYPE'));
+ }
+
+ /**
+ * Sets the default locale.
+ *
+ * @param string $locale
+ */
+ public function setDefaultLocale($locale)
+ {
+ $this->defaultLocale = $locale;
+
+ if (null === $this->locale) {
+ $this->setPhpDefaultLocale($locale);
+ }
+ }
+
+ /**
+ * Get the default locale.
+ *
+ * @return string
+ */
+ public function getDefaultLocale()
+ {
+ return $this->defaultLocale;
+ }
+
+ /**
+ * Sets the locale.
+ *
+ * @param string $locale
+ */
+ public function setLocale($locale)
+ {
+ $this->setPhpDefaultLocale($this->locale = $locale);
+ }
+
+ /**
+ * Get the locale.
+ *
+ * @return string
+ */
+ public function getLocale()
+ {
+ return null === $this->locale ? $this->defaultLocale : $this->locale;
+ }
+
+ /**
+ * Checks if the request method is of specified type.
+ *
+ * @param string $method Uppercase request method (GET, POST etc)
+ *
+ * @return bool
+ */
+ public function isMethod($method)
+ {
+ return $this->getMethod() === strtoupper($method);
+ }
+
+ /**
+ * Checks whether or not the method is safe.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+ *
+ * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default.
+ *
+ * @return bool
+ */
+ public function isMethodSafe(/* $andCacheable = true */)
+ {
+ if (!func_num_args() || func_get_arg(0)) {
+ // This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature)
+ // then setting $andCacheable to false should be deprecated in 4.1
+ @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED);
+
+ return in_array($this->getMethod(), array('GET', 'HEAD'));
+ }
+
+ return in_array($this->getMethod(), array('GET', 'HEAD', 'OPTIONS', 'TRACE'));
+ }
+
+ /**
+ * Checks whether or not the method is idempotent.
+ *
+ * @return bool
+ */
+ public function isMethodIdempotent()
+ {
+ return in_array($this->getMethod(), array('HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE'));
+ }
+
+ /**
+ * Checks whether the method is cacheable or not.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
+ *
+ * @return bool
+ */
+ public function isMethodCacheable()
+ {
+ return in_array($this->getMethod(), array('GET', 'HEAD'));
+ }
+
+ /**
+ * Returns the protocol version.
+ *
+ * If the application is behind a proxy, the protocol version used in the
+ * requests between the client and the proxy and between the proxy and the
+ * server might be different. This returns the former (from the "Via" header)
+ * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns
+ * the latter (from the "SERVER_PROTOCOL" server parameter).
+ *
+ * @return string
+ */
+ public function getProtocolVersion()
+ {
+ if ($this->isFromTrustedProxy()) {
+ preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via'), $matches);
+
+ if ($matches) {
+ return 'HTTP/'.$matches[2];
+ }
+ }
+
+ return $this->server->get('SERVER_PROTOCOL');
+ }
+
+ /**
+ * Returns the request body content.
+ *
+ * @param bool $asResource If true, a resource will be returned
+ *
+ * @return string|resource The request body content or a resource to read the body stream
+ *
+ * @throws \LogicException
+ */
+ public function getContent($asResource = false)
+ {
+ $currentContentIsResource = is_resource($this->content);
+ if (\PHP_VERSION_ID < 50600 && false === $this->content) {
+ throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.');
+ }
+
+ if (true === $asResource) {
+ if ($currentContentIsResource) {
+ rewind($this->content);
+
+ return $this->content;
+ }
+
+ // Content passed in parameter (test)
+ if (is_string($this->content)) {
+ $resource = fopen('php://temp', 'r+');
+ fwrite($resource, $this->content);
+ rewind($resource);
+
+ return $resource;
+ }
+
+ $this->content = false;
+
+ return fopen('php://input', 'rb');
+ }
+
+ if ($currentContentIsResource) {
+ rewind($this->content);
+
+ return stream_get_contents($this->content);
+ }
+
+ if (null === $this->content || false === $this->content) {
+ $this->content = file_get_contents('php://input');
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * Gets the Etags.
+ *
+ * @return array The entity tags
+ */
+ public function getETags()
+ {
+ return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNoCache()
+ {
+ return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
+ }
+
+ /**
+ * Returns the preferred language.
+ *
+ * @param array $locales An array of ordered available locales
+ *
+ * @return string|null The preferred locale
+ */
+ public function getPreferredLanguage(array $locales = null)
+ {
+ $preferredLanguages = $this->getLanguages();
+
+ if (empty($locales)) {
+ return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null;
+ }
+
+ if (!$preferredLanguages) {
+ return $locales[0];
+ }
+
+ $extendedPreferredLanguages = array();
+ foreach ($preferredLanguages as $language) {
+ $extendedPreferredLanguages[] = $language;
+ if (false !== $position = strpos($language, '_')) {
+ $superLanguage = substr($language, 0, $position);
+ if (!in_array($superLanguage, $preferredLanguages)) {
+ $extendedPreferredLanguages[] = $superLanguage;
+ }
+ }
+ }
+
+ $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales));
+
+ return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0];
+ }
+
+ /**
+ * Gets a list of languages acceptable by the client browser.
+ *
+ * @return array Languages ordered in the user browser preferences
+ */
+ public function getLanguages()
+ {
+ if (null !== $this->languages) {
+ return $this->languages;
+ }
+
+ $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
+ $this->languages = array();
+ foreach ($languages as $lang => $acceptHeaderItem) {
+ if (false !== strpos($lang, '-')) {
+ $codes = explode('-', $lang);
+ if ('i' === $codes[0]) {
+ // Language not listed in ISO 639 that are not variants
+ // of any listed language, which can be registered with the
+ // i-prefix, such as i-cherokee
+ if (count($codes) > 1) {
+ $lang = $codes[1];
+ }
+ } else {
+ for ($i = 0, $max = count($codes); $i < $max; ++$i) {
+ if (0 === $i) {
+ $lang = strtolower($codes[0]);
+ } else {
+ $lang .= '_'.strtoupper($codes[$i]);
+ }
+ }
+ }
+ }
+
+ $this->languages[] = $lang;
+ }
+
+ return $this->languages;
+ }
+
+ /**
+ * Gets a list of charsets acceptable by the client browser.
+ *
+ * @return array List of charsets in preferable order
+ */
+ public function getCharsets()
+ {
+ if (null !== $this->charsets) {
+ return $this->charsets;
+ }
+
+ return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all());
+ }
+
+ /**
+ * Gets a list of encodings acceptable by the client browser.
+ *
+ * @return array List of encodings in preferable order
+ */
+ public function getEncodings()
+ {
+ if (null !== $this->encodings) {
+ return $this->encodings;
+ }
+
+ return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all());
+ }
+
+ /**
+ * Gets a list of content types acceptable by the client browser.
+ *
+ * @return array List of content types in preferable order
+ */
+ public function getAcceptableContentTypes()
+ {
+ if (null !== $this->acceptableContentTypes) {
+ return $this->acceptableContentTypes;
+ }
+
+ return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all());
+ }
+
+ /**
+ * Returns true if the request is a XMLHttpRequest.
+ *
+ * It works if your JavaScript library sets an X-Requested-With HTTP header.
+ * It is known to work with common JavaScript frameworks:
+ *
+ * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
+ *
+ * @return bool true if the request is an XMLHttpRequest, false otherwise
+ */
+ public function isXmlHttpRequest()
+ {
+ return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
+ }
+
+ /*
+ * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
+ *
+ * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
+ *
+ * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ */
+
+ protected function prepareRequestUri()
+ {
+ $requestUri = '';
+
+ if ($this->headers->has('X_ORIGINAL_URL')) {
+ // IIS with Microsoft Rewrite Module
+ $requestUri = $this->headers->get('X_ORIGINAL_URL');
+ $this->headers->remove('X_ORIGINAL_URL');
+ $this->server->remove('HTTP_X_ORIGINAL_URL');
+ $this->server->remove('UNENCODED_URL');
+ $this->server->remove('IIS_WasUrlRewritten');
+ } elseif ($this->headers->has('X_REWRITE_URL')) {
+ // IIS with ISAPI_Rewrite
+ $requestUri = $this->headers->get('X_REWRITE_URL');
+ $this->headers->remove('X_REWRITE_URL');
+ } elseif ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
+ // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
+ $requestUri = $this->server->get('UNENCODED_URL');
+ $this->server->remove('UNENCODED_URL');
+ $this->server->remove('IIS_WasUrlRewritten');
+ } elseif ($this->server->has('REQUEST_URI')) {
+ $requestUri = $this->server->get('REQUEST_URI');
+ // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
+ $schemeAndHttpHost = $this->getSchemeAndHttpHost();
+ if (0 === strpos($requestUri, $schemeAndHttpHost)) {
+ $requestUri = substr($requestUri, strlen($schemeAndHttpHost));
+ }
+ } elseif ($this->server->has('ORIG_PATH_INFO')) {
+ // IIS 5.0, PHP as CGI
+ $requestUri = $this->server->get('ORIG_PATH_INFO');
+ if ('' != $this->server->get('QUERY_STRING')) {
+ $requestUri .= '?'.$this->server->get('QUERY_STRING');
+ }
+ $this->server->remove('ORIG_PATH_INFO');
+ }
+
+ // normalize the request URI to ease creating sub-requests from this request
+ $this->server->set('REQUEST_URI', $requestUri);
+
+ return $requestUri;
+ }
+
+ /**
+ * Prepares the base URL.
+ *
+ * @return string
+ */
+ protected function prepareBaseUrl()
+ {
+ $filename = basename($this->server->get('SCRIPT_FILENAME'));
+
+ if (basename($this->server->get('SCRIPT_NAME')) === $filename) {
+ $baseUrl = $this->server->get('SCRIPT_NAME');
+ } elseif (basename($this->server->get('PHP_SELF')) === $filename) {
+ $baseUrl = $this->server->get('PHP_SELF');
+ } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) {
+ $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
+ } else {
+ // Backtrack up the script_filename to find the portion matching
+ // php_self
+ $path = $this->server->get('PHP_SELF', '');
+ $file = $this->server->get('SCRIPT_FILENAME', '');
+ $segs = explode('/', trim($file, '/'));
+ $segs = array_reverse($segs);
+ $index = 0;
+ $last = count($segs);
+ $baseUrl = '';
+ do {
+ $seg = $segs[$index];
+ $baseUrl = '/'.$seg.$baseUrl;
+ ++$index;
+ } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
+ }
+
+ // Does the baseUrl have anything in common with the request_uri?
+ $requestUri = $this->getRequestUri();
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
+ $requestUri = '/'.$requestUri;
+ }
+
+ if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
+ // full $baseUrl matches
+ return $prefix;
+ }
+
+ if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR).'/')) {
+ // directory portion of $baseUrl matches
+ return rtrim($prefix, '/'.DIRECTORY_SEPARATOR);
+ }
+
+ $truncatedRequestUri = $requestUri;
+ if (false !== $pos = strpos($requestUri, '?')) {
+ $truncatedRequestUri = substr($requestUri, 0, $pos);
+ }
+
+ $basename = basename($baseUrl);
+ if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
+ // no match whatsoever; set it blank
+ return '';
+ }
+
+ // If using mod_rewrite or ISAPI_Rewrite strip the script filename
+ // out of baseUrl. $pos !== 0 makes sure it is not matching a value
+ // from PATH_INFO or QUERY_STRING
+ if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) {
+ $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
+ }
+
+ return rtrim($baseUrl, '/'.DIRECTORY_SEPARATOR);
+ }
+
+ /**
+ * Prepares the base path.
+ *
+ * @return string base path
+ */
+ protected function prepareBasePath()
+ {
+ $baseUrl = $this->getBaseUrl();
+ if (empty($baseUrl)) {
+ return '';
+ }
+
+ $filename = basename($this->server->get('SCRIPT_FILENAME'));
+ if (basename($baseUrl) === $filename) {
+ $basePath = dirname($baseUrl);
+ } else {
+ $basePath = $baseUrl;
+ }
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $basePath = str_replace('\\', '/', $basePath);
+ }
+
+ return rtrim($basePath, '/');
+ }
+
+ /**
+ * Prepares the path info.
+ *
+ * @return string path info
+ */
+ protected function preparePathInfo()
+ {
+ if (null === ($requestUri = $this->getRequestUri())) {
+ return '/';
+ }
+
+ // Remove the query string from REQUEST_URI
+ if (false !== $pos = strpos($requestUri, '?')) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
+ $requestUri = '/'.$requestUri;
+ }
+
+ if (null === ($baseUrl = $this->getBaseUrl())) {
+ return $requestUri;
+ }
+
+ $pathInfo = substr($requestUri, strlen($baseUrl));
+ if (false === $pathInfo || '' === $pathInfo) {
+ // If substr() returns false then PATH_INFO is set to an empty string
+ return '/';
+ }
+
+ return (string) $pathInfo;
+ }
+
+ /**
+ * Initializes HTTP request formats.
+ */
+ protected static function initializeFormats()
+ {
+ static::$formats = array(
+ 'html' => array('text/html', 'application/xhtml+xml'),
+ 'txt' => array('text/plain'),
+ 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'),
+ 'css' => array('text/css'),
+ 'json' => array('application/json', 'application/x-json'),
+ 'jsonld' => array('application/ld+json'),
+ 'xml' => array('text/xml', 'application/xml', 'application/x-xml'),
+ 'rdf' => array('application/rdf+xml'),
+ 'atom' => array('application/atom+xml'),
+ 'rss' => array('application/rss+xml'),
+ 'form' => array('application/x-www-form-urlencoded'),
+ );
+ }
+
+ /**
+ * Sets the default PHP locale.
+ *
+ * @param string $locale
+ */
+ private function setPhpDefaultLocale($locale)
+ {
+ // if either the class Locale doesn't exist, or an exception is thrown when
+ // setting the default locale, the intl module is not installed, and
+ // the call can be ignored:
+ try {
+ if (class_exists('Locale', false)) {
+ \Locale::setDefault($locale);
+ }
+ } catch (\Exception $e) {
+ }
+ }
+
+ /*
+ * Returns the prefix as encoded in the string when the string starts with
+ * the given prefix, false otherwise.
+ *
+ * @param string $string The urlencoded string
+ * @param string $prefix The prefix not encoded
+ *
+ * @return string|false The prefix as it is encoded in $string, or false
+ */
+ private function getUrlencodedPrefix($string, $prefix)
+ {
+ if (0 !== strpos(rawurldecode($string), $prefix)) {
+ return false;
+ }
+
+ $len = strlen($prefix);
+
+ if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
+ return $match[0];
+ }
+
+ return false;
+ }
+
+ private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
+ {
+ if (self::$requestFactory) {
+ $request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
+
+ if (!$request instanceof self) {
+ throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
+ }
+
+ return $request;
+ }
+
+ return new static($query, $request, $attributes, $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Indicates whether this request originated from a trusted proxy.
+ *
+ * This can be useful to determine whether or not to trust the
+ * contents of a proxy-specific header.
+ *
+ * @return bool true if the request came from a trusted proxy, false otherwise
+ */
+ public function isFromTrustedProxy()
+ {
+ return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
+ }
+
+ private function getTrustedValues($type, $ip = null)
+ {
+ $clientValues = array();
+ $forwardedValues = array();
+
+ if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
+ foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
+ $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v);
+ }
+ }
+
+ if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
+ $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
+ $forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
+ }
+
+ if (null !== $ip) {
+ $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip);
+ $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip);
+ }
+
+ if ($forwardedValues === $clientValues || !$clientValues) {
+ return $forwardedValues;
+ }
+
+ if (!$forwardedValues) {
+ return $clientValues;
+ }
+
+ if (!$this->isForwardedValid) {
+ return null !== $ip ? array('0.0.0.0', $ip) : array();
+ }
+ $this->isForwardedValid = false;
+
+ throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type]));
+ }
+
+ private function normalizeAndFilterClientIps(array $clientIps, $ip)
+ {
+ if (!$clientIps) {
+ return array();
+ }
+ $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
+ $firstTrustedIp = null;
+
+ foreach ($clientIps as $key => $clientIp) {
+ // Remove port (unfortunately, it does happen)
+ if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
+ $clientIps[$key] = $clientIp = $match[1];
+ }
+
+ if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
+ unset($clientIps[$key]);
+
+ continue;
+ }
+
+ if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
+ unset($clientIps[$key]);
+
+ // Fallback to this when the client IP falls into the range of trusted proxies
+ if (null === $firstTrustedIp) {
+ $firstTrustedIp = $clientIp;
+ }
+ }
+ }
+
+ // Now the IP chain contains only untrusted proxies and the client IP
+ return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/RequestMatcher.php b/assets/php/vendor/symfony/http-foundation/RequestMatcher.php
new file mode 100644
index 0000000..076d077
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/RequestMatcher.php
@@ -0,0 +1,178 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RequestMatcher compares a pre-defined set of checks against a Request instance.
+ *
+ * @author Fabien Potencier
+ */
+class RequestMatcher implements RequestMatcherInterface
+{
+ /**
+ * @var string|null
+ */
+ private $path;
+
+ /**
+ * @var string|null
+ */
+ private $host;
+
+ /**
+ * @var string[]
+ */
+ private $methods = array();
+
+ /**
+ * @var string[]
+ */
+ private $ips = array();
+
+ /**
+ * @var array
+ */
+ private $attributes = array();
+
+ /**
+ * @var string[]
+ */
+ private $schemes = array();
+
+ /**
+ * @param string|null $path
+ * @param string|null $host
+ * @param string|string[]|null $methods
+ * @param string|string[]|null $ips
+ * @param array $attributes
+ * @param string|string[]|null $schemes
+ */
+ public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array(), $schemes = null)
+ {
+ $this->matchPath($path);
+ $this->matchHost($host);
+ $this->matchMethod($methods);
+ $this->matchIps($ips);
+ $this->matchScheme($schemes);
+
+ foreach ($attributes as $k => $v) {
+ $this->matchAttribute($k, $v);
+ }
+ }
+
+ /**
+ * Adds a check for the HTTP scheme.
+ *
+ * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes
+ */
+ public function matchScheme($scheme)
+ {
+ $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : array();
+ }
+
+ /**
+ * Adds a check for the URL host name.
+ *
+ * @param string|null $regexp A Regexp
+ */
+ public function matchHost($regexp)
+ {
+ $this->host = $regexp;
+ }
+
+ /**
+ * Adds a check for the URL path info.
+ *
+ * @param string|null $regexp A Regexp
+ */
+ public function matchPath($regexp)
+ {
+ $this->path = $regexp;
+ }
+
+ /**
+ * Adds a check for the client IP.
+ *
+ * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+ */
+ public function matchIp($ip)
+ {
+ $this->matchIps($ip);
+ }
+
+ /**
+ * Adds a check for the client IP.
+ *
+ * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+ */
+ public function matchIps($ips)
+ {
+ $this->ips = null !== $ips ? (array) $ips : array();
+ }
+
+ /**
+ * Adds a check for the HTTP method.
+ *
+ * @param string|string[]|null $method An HTTP method or an array of HTTP methods
+ */
+ public function matchMethod($method)
+ {
+ $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : array();
+ }
+
+ /**
+ * Adds a check for request attribute.
+ *
+ * @param string $key The request attribute name
+ * @param string $regexp A Regexp
+ */
+ public function matchAttribute($key, $regexp)
+ {
+ $this->attributes[$key] = $regexp;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function matches(Request $request)
+ {
+ if ($this->schemes && !in_array($request->getScheme(), $this->schemes, true)) {
+ return false;
+ }
+
+ if ($this->methods && !in_array($request->getMethod(), $this->methods, true)) {
+ return false;
+ }
+
+ foreach ($this->attributes as $key => $pattern) {
+ if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) {
+ return false;
+ }
+ }
+
+ if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) {
+ return false;
+ }
+
+ if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) {
+ return false;
+ }
+
+ if (IpUtils::checkIp($request->getClientIp(), $this->ips)) {
+ return true;
+ }
+
+ // Note to future implementors: add additional checks above the
+ // foreach above or else your check might not be run!
+ return 0 === count($this->ips);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/RequestMatcherInterface.php b/assets/php/vendor/symfony/http-foundation/RequestMatcherInterface.php
new file mode 100644
index 0000000..c26db3e
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/RequestMatcherInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RequestMatcherInterface is an interface for strategies to match a Request.
+ *
+ * @author Fabien Potencier
+ */
+interface RequestMatcherInterface
+{
+ /**
+ * Decides whether the rule(s) implemented by the strategy matches the supplied request.
+ *
+ * @return bool true if the request matches, false otherwise
+ */
+ public function matches(Request $request);
+}
diff --git a/assets/php/vendor/symfony/http-foundation/RequestStack.php b/assets/php/vendor/symfony/http-foundation/RequestStack.php
new file mode 100644
index 0000000..3d9cfd0
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/RequestStack.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Request stack that controls the lifecycle of requests.
+ *
+ * @author Benjamin Eberlei
+ */
+class RequestStack
+{
+ /**
+ * @var Request[]
+ */
+ private $requests = array();
+
+ /**
+ * Pushes a Request on the stack.
+ *
+ * This method should generally not be called directly as the stack
+ * management should be taken care of by the application itself.
+ */
+ public function push(Request $request)
+ {
+ $this->requests[] = $request;
+ }
+
+ /**
+ * Pops the current request from the stack.
+ *
+ * This operation lets the current request go out of scope.
+ *
+ * This method should generally not be called directly as the stack
+ * management should be taken care of by the application itself.
+ *
+ * @return Request|null
+ */
+ public function pop()
+ {
+ if (!$this->requests) {
+ return;
+ }
+
+ return array_pop($this->requests);
+ }
+
+ /**
+ * @return Request|null
+ */
+ public function getCurrentRequest()
+ {
+ return end($this->requests) ?: null;
+ }
+
+ /**
+ * Gets the master Request.
+ *
+ * Be warned that making your code aware of the master request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ *
+ * @return Request|null
+ */
+ public function getMasterRequest()
+ {
+ if (!$this->requests) {
+ return;
+ }
+
+ return $this->requests[0];
+ }
+
+ /**
+ * Returns the parent request of the current.
+ *
+ * Be warned that making your code aware of the parent request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ *
+ * If current Request is the master request, it returns null.
+ *
+ * @return Request|null
+ */
+ public function getParentRequest()
+ {
+ $pos = count($this->requests) - 2;
+
+ if (!isset($this->requests[$pos])) {
+ return;
+ }
+
+ return $this->requests[$pos];
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Response.php b/assets/php/vendor/symfony/http-foundation/Response.php
new file mode 100644
index 0000000..6f8a623
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Response.php
@@ -0,0 +1,1298 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Response represents an HTTP response.
+ *
+ * @author Fabien Potencier
+ */
+class Response
+{
+ const HTTP_CONTINUE = 100;
+ const HTTP_SWITCHING_PROTOCOLS = 101;
+ const HTTP_PROCESSING = 102; // RFC2518
+ const HTTP_OK = 200;
+ const HTTP_CREATED = 201;
+ const HTTP_ACCEPTED = 202;
+ const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
+ const HTTP_NO_CONTENT = 204;
+ const HTTP_RESET_CONTENT = 205;
+ const HTTP_PARTIAL_CONTENT = 206;
+ const HTTP_MULTI_STATUS = 207; // RFC4918
+ const HTTP_ALREADY_REPORTED = 208; // RFC5842
+ const HTTP_IM_USED = 226; // RFC3229
+ const HTTP_MULTIPLE_CHOICES = 300;
+ const HTTP_MOVED_PERMANENTLY = 301;
+ const HTTP_FOUND = 302;
+ const HTTP_SEE_OTHER = 303;
+ const HTTP_NOT_MODIFIED = 304;
+ const HTTP_USE_PROXY = 305;
+ const HTTP_RESERVED = 306;
+ const HTTP_TEMPORARY_REDIRECT = 307;
+ const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
+ const HTTP_BAD_REQUEST = 400;
+ const HTTP_UNAUTHORIZED = 401;
+ const HTTP_PAYMENT_REQUIRED = 402;
+ const HTTP_FORBIDDEN = 403;
+ const HTTP_NOT_FOUND = 404;
+ const HTTP_METHOD_NOT_ALLOWED = 405;
+ const HTTP_NOT_ACCEPTABLE = 406;
+ const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
+ const HTTP_REQUEST_TIMEOUT = 408;
+ const HTTP_CONFLICT = 409;
+ const HTTP_GONE = 410;
+ const HTTP_LENGTH_REQUIRED = 411;
+ const HTTP_PRECONDITION_FAILED = 412;
+ const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
+ const HTTP_REQUEST_URI_TOO_LONG = 414;
+ const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
+ const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+ const HTTP_EXPECTATION_FAILED = 417;
+ const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
+ const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
+ const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
+ const HTTP_LOCKED = 423; // RFC4918
+ const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
+ const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
+ const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
+ const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
+ const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
+ const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
+ const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
+ const HTTP_INTERNAL_SERVER_ERROR = 500;
+ const HTTP_NOT_IMPLEMENTED = 501;
+ const HTTP_BAD_GATEWAY = 502;
+ const HTTP_SERVICE_UNAVAILABLE = 503;
+ const HTTP_GATEWAY_TIMEOUT = 504;
+ const HTTP_VERSION_NOT_SUPPORTED = 505;
+ const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
+ const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
+ const HTTP_LOOP_DETECTED = 508; // RFC5842
+ const HTTP_NOT_EXTENDED = 510; // RFC2774
+ const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
+
+ /**
+ * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag
+ */
+ public $headers;
+
+ /**
+ * @var string
+ */
+ protected $content;
+
+ /**
+ * @var string
+ */
+ protected $version;
+
+ /**
+ * @var int
+ */
+ protected $statusCode;
+
+ /**
+ * @var string
+ */
+ protected $statusText;
+
+ /**
+ * @var string
+ */
+ protected $charset;
+
+ /**
+ * Status codes translation table.
+ *
+ * The list of codes is complete according to the
+ * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry}
+ * (last updated 2016-03-01).
+ *
+ * Unless otherwise noted, the status code is defined in RFC2616.
+ *
+ * @var array
+ */
+ public static $statusTexts = array(
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing', // RFC2518
+ 103 => 'Early Hints',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status', // RFC4918
+ 208 => 'Already Reported', // RFC5842
+ 226 => 'IM Used', // RFC3229
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect', // RFC7238
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Payload Too Large',
+ 414 => 'URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot', // RFC2324
+ 421 => 'Misdirected Request', // RFC7540
+ 422 => 'Unprocessable Entity', // RFC4918
+ 423 => 'Locked', // RFC4918
+ 424 => 'Failed Dependency', // RFC4918
+ 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817
+ 426 => 'Upgrade Required', // RFC2817
+ 428 => 'Precondition Required', // RFC6585
+ 429 => 'Too Many Requests', // RFC6585
+ 431 => 'Request Header Fields Too Large', // RFC6585
+ 451 => 'Unavailable For Legal Reasons', // RFC7725
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates', // RFC2295
+ 507 => 'Insufficient Storage', // RFC4918
+ 508 => 'Loop Detected', // RFC5842
+ 510 => 'Not Extended', // RFC2774
+ 511 => 'Network Authentication Required', // RFC6585
+ );
+
+ /**
+ * @param mixed $content The response content, see setContent()
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @throws \InvalidArgumentException When the HTTP status code is not valid
+ */
+ public function __construct($content = '', $status = 200, $headers = array())
+ {
+ $this->headers = new ResponseHeaderBag($headers);
+ $this->setContent($content);
+ $this->setStatusCode($status);
+ $this->setProtocolVersion('1.0');
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * Example:
+ *
+ * return Response::create($body, 200)
+ * ->setSharedMaxAge(300);
+ *
+ * @param mixed $content The response content, see setContent()
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($content = '', $status = 200, $headers = array())
+ {
+ return new static($content, $status, $headers);
+ }
+
+ /**
+ * Returns the Response as an HTTP string.
+ *
+ * The string representation of the Response is the same as the
+ * one that will be sent to the client only if the prepare() method
+ * has been called before.
+ *
+ * @return string The Response as an HTTP string
+ *
+ * @see prepare()
+ */
+ public function __toString()
+ {
+ return
+ sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
+ $this->headers."\r\n".
+ $this->getContent();
+ }
+
+ /**
+ * Clones the current Response instance.
+ */
+ public function __clone()
+ {
+ $this->headers = clone $this->headers;
+ }
+
+ /**
+ * Prepares the Response before it is sent to the client.
+ *
+ * This method tweaks the Response to ensure that it is
+ * compliant with RFC 2616. Most of the changes are based on
+ * the Request that is "associated" with this Response.
+ *
+ * @return $this
+ */
+ public function prepare(Request $request)
+ {
+ $headers = $this->headers;
+
+ if ($this->isInformational() || $this->isEmpty()) {
+ $this->setContent(null);
+ $headers->remove('Content-Type');
+ $headers->remove('Content-Length');
+ } else {
+ // Content-type based on the Request
+ if (!$headers->has('Content-Type')) {
+ $format = $request->getRequestFormat();
+ if (null !== $format && $mimeType = $request->getMimeType($format)) {
+ $headers->set('Content-Type', $mimeType);
+ }
+ }
+
+ // Fix Content-Type
+ $charset = $this->charset ?: 'UTF-8';
+ if (!$headers->has('Content-Type')) {
+ $headers->set('Content-Type', 'text/html; charset='.$charset);
+ } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
+ // add the charset
+ $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
+ }
+
+ // Fix Content-Length
+ if ($headers->has('Transfer-Encoding')) {
+ $headers->remove('Content-Length');
+ }
+
+ if ($request->isMethod('HEAD')) {
+ // cf. RFC2616 14.13
+ $length = $headers->get('Content-Length');
+ $this->setContent(null);
+ if ($length) {
+ $headers->set('Content-Length', $length);
+ }
+ }
+ }
+
+ // Fix protocol
+ if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
+ $this->setProtocolVersion('1.1');
+ }
+
+ // Check if we need to send extra expire info headers
+ if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) {
+ $this->headers->set('pragma', 'no-cache');
+ $this->headers->set('expires', -1);
+ }
+
+ $this->ensureIEOverSSLCompatibility($request);
+
+ return $this;
+ }
+
+ /**
+ * Sends HTTP headers.
+ *
+ * @return $this
+ */
+ public function sendHeaders()
+ {
+ // headers have already been sent by the developer
+ if (headers_sent()) {
+ return $this;
+ }
+
+ // headers
+ foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
+ foreach ($values as $value) {
+ header($name.': '.$value, false, $this->statusCode);
+ }
+ }
+
+ // status
+ header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
+
+ // cookies
+ foreach ($this->headers->getCookies() as $cookie) {
+ if ($cookie->isRaw()) {
+ setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
+ } else {
+ setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sends content for the current web response.
+ *
+ * @return $this
+ */
+ public function sendContent()
+ {
+ echo $this->content;
+
+ return $this;
+ }
+
+ /**
+ * Sends HTTP headers and content.
+ *
+ * @return $this
+ */
+ public function send()
+ {
+ $this->sendHeaders();
+ $this->sendContent();
+
+ if (function_exists('fastcgi_finish_request')) {
+ fastcgi_finish_request();
+ } elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
+ static::closeOutputBuffers(0, true);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the response content.
+ *
+ * Valid types are strings, numbers, null, and objects that implement a __toString() method.
+ *
+ * @param mixed $content Content that can be cast to string
+ *
+ * @return $this
+ *
+ * @throws \UnexpectedValueException
+ */
+ public function setContent($content)
+ {
+ if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) {
+ throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content)));
+ }
+
+ $this->content = (string) $content;
+
+ return $this;
+ }
+
+ /**
+ * Gets the current response content.
+ *
+ * @return string Content
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * Sets the HTTP protocol version (1.0 or 1.1).
+ *
+ * @param string $version The HTTP protocol version
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setProtocolVersion($version)
+ {
+ $this->version = $version;
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTP protocol version.
+ *
+ * @return string The HTTP protocol version
+ *
+ * @final since version 3.2
+ */
+ public function getProtocolVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Sets the response status code.
+ *
+ * If the status text is null it will be automatically populated for the known
+ * status codes and left empty otherwise.
+ *
+ * @param int $code HTTP status code
+ * @param mixed $text HTTP status text
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException When the HTTP status code is not valid
+ *
+ * @final since version 3.2
+ */
+ public function setStatusCode($code, $text = null)
+ {
+ $this->statusCode = $code = (int) $code;
+ if ($this->isInvalid()) {
+ throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
+ }
+
+ if (null === $text) {
+ $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status';
+
+ return $this;
+ }
+
+ if (false === $text) {
+ $this->statusText = '';
+
+ return $this;
+ }
+
+ $this->statusText = $text;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the status code for the current web response.
+ *
+ * @return int Status code
+ *
+ * @final since version 3.2
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * Sets the response charset.
+ *
+ * @param string $charset Character set
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the response charset.
+ *
+ * @return string Character set
+ *
+ * @final since version 3.2
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Returns true if the response is worth caching under any circumstance.
+ *
+ * Responses marked "private" with an explicit Cache-Control directive are
+ * considered uncacheable.
+ *
+ * Responses with neither a freshness lifetime (Expires, max-age) nor cache
+ * validator (Last-Modified, ETag) are considered uncacheable.
+ *
+ * @return bool true if the response is worth caching, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function isCacheable()
+ {
+ if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) {
+ return false;
+ }
+
+ if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
+ return false;
+ }
+
+ return $this->isValidateable() || $this->isFresh();
+ }
+
+ /**
+ * Returns true if the response is "fresh".
+ *
+ * Fresh responses may be served from cache without any interaction with the
+ * origin. A response is considered fresh when it includes a Cache-Control/max-age
+ * indicator or Expires header and the calculated age is less than the freshness lifetime.
+ *
+ * @return bool true if the response is fresh, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function isFresh()
+ {
+ return $this->getTtl() > 0;
+ }
+
+ /**
+ * Returns true if the response includes headers that can be used to validate
+ * the response with the origin server using a conditional GET request.
+ *
+ * @return bool true if the response is validateable, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function isValidateable()
+ {
+ return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
+ }
+
+ /**
+ * Marks the response as "private".
+ *
+ * It makes the response ineligible for serving other clients.
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setPrivate()
+ {
+ $this->headers->removeCacheControlDirective('public');
+ $this->headers->addCacheControlDirective('private');
+
+ return $this;
+ }
+
+ /**
+ * Marks the response as "public".
+ *
+ * It makes the response eligible for serving other clients.
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setPublic()
+ {
+ $this->headers->addCacheControlDirective('public');
+ $this->headers->removeCacheControlDirective('private');
+
+ return $this;
+ }
+
+ /**
+ * Marks the response as "immutable".
+ *
+ * @param bool $immutable enables or disables the immutable directive
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setImmutable($immutable = true)
+ {
+ if ($immutable) {
+ $this->headers->addCacheControlDirective('immutable');
+ } else {
+ $this->headers->removeCacheControlDirective('immutable');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response is marked as "immutable".
+ *
+ * @return bool returns true if the response is marked as "immutable"; otherwise false
+ *
+ * @final
+ */
+ public function isImmutable()
+ {
+ return $this->headers->hasCacheControlDirective('immutable');
+ }
+
+ /**
+ * Returns true if the response must be revalidated by caches.
+ *
+ * This method indicates that the response must not be served stale by a
+ * cache in any circumstance without first revalidating with the origin.
+ * When present, the TTL of the response should not be overridden to be
+ * greater than the value provided by the origin.
+ *
+ * @return bool true if the response must be revalidated by a cache, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function mustRevalidate()
+ {
+ return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
+ }
+
+ /**
+ * Returns the Date header as a DateTime instance.
+ *
+ * @return \DateTime A \DateTime instance
+ *
+ * @throws \RuntimeException When the header is not parseable
+ *
+ * @final since version 3.2
+ */
+ public function getDate()
+ {
+ return $this->headers->getDate('Date');
+ }
+
+ /**
+ * Sets the Date header.
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setDate(\DateTime $date)
+ {
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
+
+ return $this;
+ }
+
+ /**
+ * Returns the age of the response.
+ *
+ * @return int The age of the response in seconds
+ *
+ * @final since version 3.2
+ */
+ public function getAge()
+ {
+ if (null !== $age = $this->headers->get('Age')) {
+ return (int) $age;
+ }
+
+ return max(time() - $this->getDate()->format('U'), 0);
+ }
+
+ /**
+ * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
+ *
+ * @return $this
+ */
+ public function expire()
+ {
+ if ($this->isFresh()) {
+ $this->headers->set('Age', $this->getMaxAge());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the value of the Expires header as a DateTime instance.
+ *
+ * @return \DateTime|null A DateTime instance or null if the header does not exist
+ *
+ * @final since version 3.2
+ */
+ public function getExpires()
+ {
+ try {
+ return $this->headers->getDate('Expires');
+ } catch (\RuntimeException $e) {
+ // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
+ return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
+ }
+ }
+
+ /**
+ * Sets the Expires HTTP header with a DateTime instance.
+ *
+ * Passing null as value will remove the header.
+ *
+ * @param \DateTime|null $date A \DateTime instance or null to remove the header
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setExpires(\DateTime $date = null)
+ {
+ if (null === $date) {
+ $this->headers->remove('Expires');
+ } else {
+ $date = clone $date;
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the number of seconds after the time specified in the response's Date
+ * header when the response should no longer be considered fresh.
+ *
+ * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
+ * back on an expires header. It returns null when no maximum age can be established.
+ *
+ * @return int|null Number of seconds
+ *
+ * @final since version 3.2
+ */
+ public function getMaxAge()
+ {
+ if ($this->headers->hasCacheControlDirective('s-maxage')) {
+ return (int) $this->headers->getCacheControlDirective('s-maxage');
+ }
+
+ if ($this->headers->hasCacheControlDirective('max-age')) {
+ return (int) $this->headers->getCacheControlDirective('max-age');
+ }
+
+ if (null !== $this->getExpires()) {
+ return $this->getExpires()->format('U') - $this->getDate()->format('U');
+ }
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be considered fresh.
+ *
+ * This methods sets the Cache-Control max-age directive.
+ *
+ * @param int $value Number of seconds
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setMaxAge($value)
+ {
+ $this->headers->addCacheControlDirective('max-age', $value);
+
+ return $this;
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
+ *
+ * This methods sets the Cache-Control s-maxage directive.
+ *
+ * @param int $value Number of seconds
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setSharedMaxAge($value)
+ {
+ $this->setPublic();
+ $this->headers->addCacheControlDirective('s-maxage', $value);
+
+ return $this;
+ }
+
+ /**
+ * Returns the response's time-to-live in seconds.
+ *
+ * It returns null when no freshness information is present in the response.
+ *
+ * When the responses TTL is <= 0, the response may not be served from cache without first
+ * revalidating with the origin.
+ *
+ * @return int|null The TTL in seconds
+ *
+ * @final since version 3.2
+ */
+ public function getTtl()
+ {
+ if (null !== $maxAge = $this->getMaxAge()) {
+ return $maxAge - $this->getAge();
+ }
+ }
+
+ /**
+ * Sets the response's time-to-live for shared caches.
+ *
+ * This method adjusts the Cache-Control/s-maxage directive.
+ *
+ * @param int $seconds Number of seconds
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setTtl($seconds)
+ {
+ $this->setSharedMaxAge($this->getAge() + $seconds);
+
+ return $this;
+ }
+
+ /**
+ * Sets the response's time-to-live for private/client caches.
+ *
+ * This method adjusts the Cache-Control/max-age directive.
+ *
+ * @param int $seconds Number of seconds
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setClientTtl($seconds)
+ {
+ $this->setMaxAge($this->getAge() + $seconds);
+
+ return $this;
+ }
+
+ /**
+ * Returns the Last-Modified HTTP header as a DateTime instance.
+ *
+ * @return \DateTime|null A DateTime instance or null if the header does not exist
+ *
+ * @throws \RuntimeException When the HTTP header is not parseable
+ *
+ * @final since version 3.2
+ */
+ public function getLastModified()
+ {
+ return $this->headers->getDate('Last-Modified');
+ }
+
+ /**
+ * Sets the Last-Modified HTTP header with a DateTime instance.
+ *
+ * Passing null as value will remove the header.
+ *
+ * @param \DateTime|null $date A \DateTime instance or null to remove the header
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setLastModified(\DateTime $date = null)
+ {
+ if (null === $date) {
+ $this->headers->remove('Last-Modified');
+ } else {
+ $date = clone $date;
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the literal value of the ETag HTTP header.
+ *
+ * @return string|null The ETag HTTP header or null if it does not exist
+ *
+ * @final since version 3.2
+ */
+ public function getEtag()
+ {
+ return $this->headers->get('ETag');
+ }
+
+ /**
+ * Sets the ETag value.
+ *
+ * @param string|null $etag The ETag unique identifier or null to remove the header
+ * @param bool $weak Whether you want a weak ETag or not
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setEtag($etag = null, $weak = false)
+ {
+ if (null === $etag) {
+ $this->headers->remove('Etag');
+ } else {
+ if (0 !== strpos($etag, '"')) {
+ $etag = '"'.$etag.'"';
+ }
+
+ $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the response's cache headers (validation and/or expiration).
+ *
+ * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
+ *
+ * @param array $options An array of cache options
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @final since version 3.3
+ */
+ public function setCache(array $options)
+ {
+ if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'))) {
+ throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff))));
+ }
+
+ if (isset($options['etag'])) {
+ $this->setEtag($options['etag']);
+ }
+
+ if (isset($options['last_modified'])) {
+ $this->setLastModified($options['last_modified']);
+ }
+
+ if (isset($options['max_age'])) {
+ $this->setMaxAge($options['max_age']);
+ }
+
+ if (isset($options['s_maxage'])) {
+ $this->setSharedMaxAge($options['s_maxage']);
+ }
+
+ if (isset($options['public'])) {
+ if ($options['public']) {
+ $this->setPublic();
+ } else {
+ $this->setPrivate();
+ }
+ }
+
+ if (isset($options['private'])) {
+ if ($options['private']) {
+ $this->setPrivate();
+ } else {
+ $this->setPublic();
+ }
+ }
+
+ if (isset($options['immutable'])) {
+ $this->setImmutable((bool) $options['immutable']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Modifies the response so that it conforms to the rules defined for a 304 status code.
+ *
+ * This sets the status, removes the body, and discards any headers
+ * that MUST NOT be included in 304 responses.
+ *
+ * @return $this
+ *
+ * @see http://tools.ietf.org/html/rfc2616#section-10.3.5
+ *
+ * @final since version 3.3
+ */
+ public function setNotModified()
+ {
+ $this->setStatusCode(304);
+ $this->setContent(null);
+
+ // remove headers that MUST NOT be included with 304 Not Modified responses
+ foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) {
+ $this->headers->remove($header);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response includes a Vary header.
+ *
+ * @return bool true if the response includes a Vary header, false otherwise
+ *
+ * @final since version 3.2
+ */
+ public function hasVary()
+ {
+ return null !== $this->headers->get('Vary');
+ }
+
+ /**
+ * Returns an array of header names given in the Vary header.
+ *
+ * @return array An array of Vary names
+ *
+ * @final since version 3.2
+ */
+ public function getVary()
+ {
+ if (!$vary = $this->headers->get('Vary', null, false)) {
+ return array();
+ }
+
+ $ret = array();
+ foreach ($vary as $item) {
+ $ret = array_merge($ret, preg_split('/[\s,]+/', $item));
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Sets the Vary header.
+ *
+ * @param string|array $headers
+ * @param bool $replace Whether to replace the actual value or not (true by default)
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setVary($headers, $replace = true)
+ {
+ $this->headers->set('Vary', $headers, $replace);
+
+ return $this;
+ }
+
+ /**
+ * Determines if the Response validators (ETag, Last-Modified) match
+ * a conditional value specified in the Request.
+ *
+ * If the Response is not modified, it sets the status code to 304 and
+ * removes the actual content by calling the setNotModified() method.
+ *
+ * @return bool true if the Response validators match the Request, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function isNotModified(Request $request)
+ {
+ if (!$request->isMethodCacheable()) {
+ return false;
+ }
+
+ $notModified = false;
+ $lastModified = $this->headers->get('Last-Modified');
+ $modifiedSince = $request->headers->get('If-Modified-Since');
+
+ if ($etags = $request->getETags()) {
+ $notModified = in_array($this->getEtag(), $etags) || in_array('*', $etags);
+ }
+
+ if ($modifiedSince && $lastModified) {
+ $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified);
+ }
+
+ if ($notModified) {
+ $this->setNotModified();
+ }
+
+ return $notModified;
+ }
+
+ /**
+ * Is response invalid?
+ *
+ * @return bool
+ *
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ *
+ * @final since version 3.2
+ */
+ public function isInvalid()
+ {
+ return $this->statusCode < 100 || $this->statusCode >= 600;
+ }
+
+ /**
+ * Is response informative?
+ *
+ * @return bool
+ *
+ * @final since version 3.3
+ */
+ public function isInformational()
+ {
+ return $this->statusCode >= 100 && $this->statusCode < 200;
+ }
+
+ /**
+ * Is response successful?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isSuccessful()
+ {
+ return $this->statusCode >= 200 && $this->statusCode < 300;
+ }
+
+ /**
+ * Is the response a redirect?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isRedirection()
+ {
+ return $this->statusCode >= 300 && $this->statusCode < 400;
+ }
+
+ /**
+ * Is there a client error?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isClientError()
+ {
+ return $this->statusCode >= 400 && $this->statusCode < 500;
+ }
+
+ /**
+ * Was there a server side error?
+ *
+ * @return bool
+ *
+ * @final since version 3.3
+ */
+ public function isServerError()
+ {
+ return $this->statusCode >= 500 && $this->statusCode < 600;
+ }
+
+ /**
+ * Is the response OK?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isOk()
+ {
+ return 200 === $this->statusCode;
+ }
+
+ /**
+ * Is the response forbidden?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isForbidden()
+ {
+ return 403 === $this->statusCode;
+ }
+
+ /**
+ * Is the response a not found error?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isNotFound()
+ {
+ return 404 === $this->statusCode;
+ }
+
+ /**
+ * Is the response a redirect of some form?
+ *
+ * @param string $location
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isRedirect($location = null)
+ {
+ return in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location'));
+ }
+
+ /**
+ * Is the response empty?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isEmpty()
+ {
+ return in_array($this->statusCode, array(204, 304));
+ }
+
+ /**
+ * Cleans or flushes output buffers up to target level.
+ *
+ * Resulting level can be greater than target level if a non-removable buffer has been encountered.
+ *
+ * @param int $targetLevel The target output buffering level
+ * @param bool $flush Whether to flush or clean the buffers
+ *
+ * @final since version 3.3
+ */
+ public static function closeOutputBuffers($targetLevel, $flush)
+ {
+ $status = ob_get_status(true);
+ $level = count($status);
+ // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
+ $flags = defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
+
+ while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
+ if ($flush) {
+ ob_end_flush();
+ } else {
+ ob_end_clean();
+ }
+ }
+ }
+
+ /**
+ * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
+ *
+ * @see http://support.microsoft.com/kb/323308
+ *
+ * @final since version 3.3
+ */
+ protected function ensureIEOverSSLCompatibility(Request $request)
+ {
+ if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) {
+ if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
+ $this->headers->remove('Cache-Control');
+ }
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/ResponseHeaderBag.php b/assets/php/vendor/symfony/http-foundation/ResponseHeaderBag.php
new file mode 100644
index 0000000..11a8593
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/ResponseHeaderBag.php
@@ -0,0 +1,340 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ResponseHeaderBag is a container for Response HTTP headers.
+ *
+ * @author Fabien Potencier
+ */
+class ResponseHeaderBag extends HeaderBag
+{
+ const COOKIES_FLAT = 'flat';
+ const COOKIES_ARRAY = 'array';
+
+ const DISPOSITION_ATTACHMENT = 'attachment';
+ const DISPOSITION_INLINE = 'inline';
+
+ protected $computedCacheControl = array();
+ protected $cookies = array();
+ protected $headerNames = array();
+
+ public function __construct(array $headers = array())
+ {
+ parent::__construct($headers);
+
+ if (!isset($this->headers['cache-control'])) {
+ $this->set('Cache-Control', '');
+ }
+
+ /* RFC2616 - 14.18 says all Responses need to have a Date */
+ if (!isset($this->headers['date'])) {
+ $this->initDate();
+ }
+ }
+
+ /**
+ * Returns the headers, with original capitalizations.
+ *
+ * @return array An array of headers
+ */
+ public function allPreserveCase()
+ {
+ $headers = array();
+ foreach ($this->all() as $name => $value) {
+ $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value;
+ }
+
+ return $headers;
+ }
+
+ public function allPreserveCaseWithoutCookies()
+ {
+ $headers = $this->allPreserveCase();
+ if (isset($this->headerNames['set-cookie'])) {
+ unset($headers[$this->headerNames['set-cookie']]);
+ }
+
+ return $headers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $headers = array())
+ {
+ $this->headerNames = array();
+
+ parent::replace($headers);
+
+ if (!isset($this->headers['cache-control'])) {
+ $this->set('Cache-Control', '');
+ }
+
+ if (!isset($this->headers['date'])) {
+ $this->initDate();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ $headers = parent::all();
+ foreach ($this->getCookies() as $cookie) {
+ $headers['set-cookie'][] = (string) $cookie;
+ }
+
+ return $headers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($key, $values, $replace = true)
+ {
+ $uniqueKey = str_replace('_', '-', strtolower($key));
+
+ if ('set-cookie' === $uniqueKey) {
+ if ($replace) {
+ $this->cookies = array();
+ }
+ foreach ((array) $values as $cookie) {
+ $this->setCookie(Cookie::fromString($cookie));
+ }
+ $this->headerNames[$uniqueKey] = $key;
+
+ return;
+ }
+
+ $this->headerNames[$uniqueKey] = $key;
+
+ parent::set($key, $values, $replace);
+
+ // ensure the cache-control header has sensible defaults
+ if (\in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'), true)) {
+ $computed = $this->computeCacheControlValue();
+ $this->headers['cache-control'] = array($computed);
+ $this->headerNames['cache-control'] = 'Cache-Control';
+ $this->computedCacheControl = $this->parseCacheControl($computed);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($key)
+ {
+ $uniqueKey = str_replace('_', '-', strtolower($key));
+ unset($this->headerNames[$uniqueKey]);
+
+ if ('set-cookie' === $uniqueKey) {
+ $this->cookies = array();
+
+ return;
+ }
+
+ parent::remove($key);
+
+ if ('cache-control' === $uniqueKey) {
+ $this->computedCacheControl = array();
+ }
+
+ if ('date' === $uniqueKey) {
+ $this->initDate();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasCacheControlDirective($key)
+ {
+ return array_key_exists($key, $this->computedCacheControl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheControlDirective($key)
+ {
+ return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
+ }
+
+ public function setCookie(Cookie $cookie)
+ {
+ $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
+ $this->headerNames['set-cookie'] = 'Set-Cookie';
+ }
+
+ /**
+ * Removes a cookie from the array, but does not unset it in the browser.
+ *
+ * @param string $name
+ * @param string $path
+ * @param string $domain
+ */
+ public function removeCookie($name, $path = '/', $domain = null)
+ {
+ if (null === $path) {
+ $path = '/';
+ }
+
+ unset($this->cookies[$domain][$path][$name]);
+
+ if (empty($this->cookies[$domain][$path])) {
+ unset($this->cookies[$domain][$path]);
+
+ if (empty($this->cookies[$domain])) {
+ unset($this->cookies[$domain]);
+ }
+ }
+
+ if (empty($this->cookies)) {
+ unset($this->headerNames['set-cookie']);
+ }
+ }
+
+ /**
+ * Returns an array with all cookies.
+ *
+ * @param string $format
+ *
+ * @return array
+ *
+ * @throws \InvalidArgumentException When the $format is invalid
+ */
+ public function getCookies($format = self::COOKIES_FLAT)
+ {
+ if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
+ throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
+ }
+
+ if (self::COOKIES_ARRAY === $format) {
+ return $this->cookies;
+ }
+
+ $flattenedCookies = array();
+ foreach ($this->cookies as $path) {
+ foreach ($path as $cookies) {
+ foreach ($cookies as $cookie) {
+ $flattenedCookies[] = $cookie;
+ }
+ }
+ }
+
+ return $flattenedCookies;
+ }
+
+ /**
+ * Clears a cookie in the browser.
+ *
+ * @param string $name
+ * @param string $path
+ * @param string $domain
+ * @param bool $secure
+ * @param bool $httpOnly
+ */
+ public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true)
+ {
+ $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly));
+ }
+
+ /**
+ * Generates a HTTP Content-Disposition field-value.
+ *
+ * @param string $disposition One of "inline" or "attachment"
+ * @param string $filename A unicode string
+ * @param string $filenameFallback A string containing only ASCII characters that
+ * is semantically equivalent to $filename. If the filename is already ASCII,
+ * it can be omitted, or just copied from $filename
+ *
+ * @return string A string suitable for use as a Content-Disposition field-value
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @see RFC 6266
+ */
+ public function makeDisposition($disposition, $filename, $filenameFallback = '')
+ {
+ if (!in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) {
+ throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
+ }
+
+ if ('' == $filenameFallback) {
+ $filenameFallback = $filename;
+ }
+
+ // filenameFallback is not ASCII.
+ if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
+ throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
+ }
+
+ // percent characters aren't safe in fallback.
+ if (false !== strpos($filenameFallback, '%')) {
+ throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
+ }
+
+ // path separators aren't allowed in either.
+ if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
+ throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
+ }
+
+ $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
+
+ if ($filename !== $filenameFallback) {
+ $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
+ }
+
+ return $output;
+ }
+
+ /**
+ * Returns the calculated value of the cache-control header.
+ *
+ * This considers several other headers and calculates or modifies the
+ * cache-control header to a sensible, conservative value.
+ *
+ * @return string
+ */
+ protected function computeCacheControlValue()
+ {
+ if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
+ return 'no-cache, private';
+ }
+
+ if (!$this->cacheControl) {
+ // conservative by default
+ return 'private, must-revalidate';
+ }
+
+ $header = $this->getCacheControlHeader();
+ if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
+ return $header;
+ }
+
+ // public if s-maxage is defined, private otherwise
+ if (!isset($this->cacheControl['s-maxage'])) {
+ return $header.', private';
+ }
+
+ return $header;
+ }
+
+ private function initDate()
+ {
+ $now = \DateTime::createFromFormat('U', time());
+ $now->setTimezone(new \DateTimeZone('UTC'));
+ $this->set('Date', $now->format('D, d M Y H:i:s').' GMT');
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/ServerBag.php b/assets/php/vendor/symfony/http-foundation/ServerBag.php
new file mode 100644
index 0000000..19d2022
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/ServerBag.php
@@ -0,0 +1,102 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ServerBag is a container for HTTP headers from the $_SERVER variable.
+ *
+ * @author Fabien Potencier
+ * @author Bulat Shakirzyanov
+ * @author Robert Kiss
+ */
+class ServerBag extends ParameterBag
+{
+ /**
+ * Gets the HTTP headers.
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ $headers = array();
+ $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true);
+ foreach ($this->parameters as $key => $value) {
+ if (0 === strpos($key, 'HTTP_')) {
+ $headers[substr($key, 5)] = $value;
+ }
+ // CONTENT_* are not prefixed with HTTP_
+ elseif (isset($contentHeaders[$key])) {
+ $headers[$key] = $value;
+ }
+ }
+
+ if (isset($this->parameters['PHP_AUTH_USER'])) {
+ $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
+ $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
+ } else {
+ /*
+ * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
+ * For this workaround to work, add these lines to your .htaccess file:
+ * RewriteCond %{HTTP:Authorization} ^(.+)$
+ * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+ *
+ * A sample .htaccess file:
+ * RewriteEngine On
+ * RewriteCond %{HTTP:Authorization} ^(.+)$
+ * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+ * RewriteCond %{REQUEST_FILENAME} !-f
+ * RewriteRule ^(.*)$ app.php [QSA,L]
+ */
+
+ $authorizationHeader = null;
+ if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
+ $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
+ } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
+ }
+
+ if (null !== $authorizationHeader) {
+ if (0 === stripos($authorizationHeader, 'basic ')) {
+ // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
+ $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
+ if (2 == count($exploded)) {
+ list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
+ }
+ } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
+ // In some circumstances PHP_AUTH_DIGEST needs to be set
+ $headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
+ $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
+ } elseif (0 === stripos($authorizationHeader, 'bearer ')) {
+ /*
+ * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
+ * I'll just set $headers['AUTHORIZATION'] here.
+ * http://php.net/manual/en/reserved.variables.server.php
+ */
+ $headers['AUTHORIZATION'] = $authorizationHeader;
+ }
+ }
+ }
+
+ if (isset($headers['AUTHORIZATION'])) {
+ return $headers;
+ }
+
+ // PHP_AUTH_USER/PHP_AUTH_PW
+ if (isset($headers['PHP_AUTH_USER'])) {
+ $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
+ } elseif (isset($headers['PHP_AUTH_DIGEST'])) {
+ $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
+ }
+
+ return $headers;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php
new file mode 100644
index 0000000..ea1fda2
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php
@@ -0,0 +1,148 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+/**
+ * This class relates to session attribute storage.
+ */
+class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
+{
+ private $name = 'attributes';
+ private $storageKey;
+
+ protected $attributes = array();
+
+ /**
+ * @param string $storageKey The key used to store attributes in the session
+ */
+ public function __construct($storageKey = '_sf2_attributes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$attributes)
+ {
+ $this->attributes = &$attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->attributes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name, $default = null)
+ {
+ return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $attributes)
+ {
+ $this->attributes = array();
+ foreach ($attributes as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($name)
+ {
+ $retval = null;
+ if (array_key_exists($name, $this->attributes)) {
+ $retval = $this->attributes[$name];
+ unset($this->attributes[$name]);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ $return = $this->attributes;
+ $this->attributes = array();
+
+ return $return;
+ }
+
+ /**
+ * Returns an iterator for attributes.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->attributes);
+ }
+
+ /**
+ * Returns the number of attributes.
+ *
+ * @return int The number of attributes
+ */
+ public function count()
+ {
+ return count($this->attributes);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
new file mode 100644
index 0000000..0d8d179
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * Attributes store.
+ *
+ * @author Drak
+ */
+interface AttributeBagInterface extends SessionBagInterface
+{
+ /**
+ * Checks if an attribute is defined.
+ *
+ * @param string $name The attribute name
+ *
+ * @return bool true if the attribute is defined, false otherwise
+ */
+ public function has($name);
+
+ /**
+ * Returns an attribute.
+ *
+ * @param string $name The attribute name
+ * @param mixed $default The default value if not found
+ *
+ * @return mixed
+ */
+ public function get($name, $default = null);
+
+ /**
+ * Sets an attribute.
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function set($name, $value);
+
+ /**
+ * Returns attributes.
+ *
+ * @return array Attributes
+ */
+ public function all();
+
+ /**
+ * Sets attributes.
+ *
+ * @param array $attributes Attributes
+ */
+ public function replace(array $attributes);
+
+ /**
+ * Removes an attribute.
+ *
+ * @param string $name
+ *
+ * @return mixed The removed value or null when it does not exist
+ */
+ public function remove($name);
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/assets/php/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
new file mode 100644
index 0000000..abbf37e
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+/**
+ * This class provides structured storage of session attributes using
+ * a name spacing character in the key.
+ *
+ * @author Drak
+ */
+class NamespacedAttributeBag extends AttributeBag
+{
+ private $namespaceCharacter;
+
+ /**
+ * @param string $storageKey Session storage key
+ * @param string $namespaceCharacter Namespace character to use in keys
+ */
+ public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/')
+ {
+ $this->namespaceCharacter = $namespaceCharacter;
+ parent::__construct($storageKey);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
+ $attributes = $this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+
+ if (null === $attributes) {
+ return false;
+ }
+
+ return array_key_exists($name, $attributes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name, $default = null)
+ {
+ // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
+ $attributes = $this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+
+ if (null === $attributes) {
+ return $default;
+ }
+
+ return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($name, $value)
+ {
+ $attributes = &$this->resolveAttributePath($name, true);
+ $name = $this->resolveKey($name);
+ $attributes[$name] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($name)
+ {
+ $retval = null;
+ $attributes = &$this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+ if (null !== $attributes && array_key_exists($name, $attributes)) {
+ $retval = $attributes[$name];
+ unset($attributes[$name]);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Resolves a path in attributes property and returns it as a reference.
+ *
+ * This method allows structured namespacing of session attributes.
+ *
+ * @param string $name Key name
+ * @param bool $writeContext Write context, default false
+ *
+ * @return array
+ */
+ protected function &resolveAttributePath($name, $writeContext = false)
+ {
+ $array = &$this->attributes;
+ $name = (0 === strpos($name, $this->namespaceCharacter)) ? substr($name, 1) : $name;
+
+ // Check if there is anything to do, else return
+ if (!$name) {
+ return $array;
+ }
+
+ $parts = explode($this->namespaceCharacter, $name);
+ if (count($parts) < 2) {
+ if (!$writeContext) {
+ return $array;
+ }
+
+ $array[$parts[0]] = array();
+
+ return $array;
+ }
+
+ unset($parts[count($parts) - 1]);
+
+ foreach ($parts as $part) {
+ if (null !== $array && !array_key_exists($part, $array)) {
+ $array[$part] = $writeContext ? array() : null;
+ }
+
+ $array = &$array[$part];
+ }
+
+ return $array;
+ }
+
+ /**
+ * Resolves the key from the name.
+ *
+ * This is the last part in a dot separated string.
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ protected function resolveKey($name)
+ {
+ if (false !== $pos = strrpos($name, $this->namespaceCharacter)) {
+ $name = substr($name, $pos + 1);
+ }
+
+ return $name;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/assets/php/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
new file mode 100644
index 0000000..77521c2
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
@@ -0,0 +1,161 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+/**
+ * AutoExpireFlashBag flash message container.
+ *
+ * @author Drak
+ */
+class AutoExpireFlashBag implements FlashBagInterface
+{
+ private $name = 'flashes';
+ private $flashes = array('display' => array(), 'new' => array());
+ private $storageKey;
+
+ /**
+ * @param string $storageKey The key used to store flashes in the session
+ */
+ public function __construct($storageKey = '_symfony_flashes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$flashes)
+ {
+ $this->flashes = &$flashes;
+
+ // The logic: messages from the last request will be stored in new, so we move them to previous
+ // This request we will show what is in 'display'. What is placed into 'new' this time round will
+ // be moved to display next time round.
+ $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array();
+ $this->flashes['new'] = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($type, $message)
+ {
+ $this->flashes['new'][$type][] = $message;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peek($type, array $default = array())
+ {
+ return $this->has($type) ? $this->flashes['display'][$type] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peekAll()
+ {
+ return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($type, array $default = array())
+ {
+ $return = $default;
+
+ if (!$this->has($type)) {
+ return $return;
+ }
+
+ if (isset($this->flashes['display'][$type])) {
+ $return = $this->flashes['display'][$type];
+ unset($this->flashes['display'][$type]);
+ }
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ $return = $this->flashes['display'];
+ $this->flashes['display'] = array();
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAll(array $messages)
+ {
+ $this->flashes['new'] = $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($type, $messages)
+ {
+ $this->flashes['new'][$type] = (array) $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($type)
+ {
+ return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function keys()
+ {
+ return array_keys($this->flashes['display']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->all();
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBag.php
new file mode 100644
index 0000000..12fb740
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBag.php
@@ -0,0 +1,152 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+/**
+ * FlashBag flash message container.
+ *
+ * @author Drak
+ */
+class FlashBag implements FlashBagInterface
+{
+ private $name = 'flashes';
+ private $flashes = array();
+ private $storageKey;
+
+ /**
+ * @param string $storageKey The key used to store flashes in the session
+ */
+ public function __construct($storageKey = '_symfony_flashes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$flashes)
+ {
+ $this->flashes = &$flashes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($type, $message)
+ {
+ $this->flashes[$type][] = $message;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peek($type, array $default = array())
+ {
+ return $this->has($type) ? $this->flashes[$type] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peekAll()
+ {
+ return $this->flashes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($type, array $default = array())
+ {
+ if (!$this->has($type)) {
+ return $default;
+ }
+
+ $return = $this->flashes[$type];
+
+ unset($this->flashes[$type]);
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ $return = $this->peekAll();
+ $this->flashes = array();
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($type, $messages)
+ {
+ $this->flashes[$type] = (array) $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAll(array $messages)
+ {
+ $this->flashes = $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($type)
+ {
+ return array_key_exists($type, $this->flashes) && $this->flashes[$type];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function keys()
+ {
+ return array_keys($this->flashes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->all();
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php
new file mode 100644
index 0000000..80e97f1
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * FlashBagInterface.
+ *
+ * @author Drak
+ */
+interface FlashBagInterface extends SessionBagInterface
+{
+ /**
+ * Adds a flash message for type.
+ *
+ * @param string $type
+ * @param string $message
+ */
+ public function add($type, $message);
+
+ /**
+ * Registers a message for a given type.
+ *
+ * @param string $type
+ * @param string|array $message
+ */
+ public function set($type, $message);
+
+ /**
+ * Gets flash messages for a given type.
+ *
+ * @param string $type Message category type
+ * @param array $default Default value if $type does not exist
+ *
+ * @return array
+ */
+ public function peek($type, array $default = array());
+
+ /**
+ * Gets all flash messages.
+ *
+ * @return array
+ */
+ public function peekAll();
+
+ /**
+ * Gets and clears flash from the stack.
+ *
+ * @param string $type
+ * @param array $default Default value if $type does not exist
+ *
+ * @return array
+ */
+ public function get($type, array $default = array());
+
+ /**
+ * Gets and clears flashes from the stack.
+ *
+ * @return array
+ */
+ public function all();
+
+ /**
+ * Sets all flash messages.
+ */
+ public function setAll(array $messages);
+
+ /**
+ * Has flash messages for a given type?
+ *
+ * @param string $type
+ *
+ * @return bool
+ */
+ public function has($type);
+
+ /**
+ * Returns a list of all defined types.
+ *
+ * @return array
+ */
+ public function keys();
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Session.php b/assets/php/vendor/symfony/http-foundation/Session/Session.php
new file mode 100644
index 0000000..a46cffb
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Session.php
@@ -0,0 +1,273 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+/**
+ * @author Fabien Potencier
+ * @author Drak
+ */
+class Session implements SessionInterface, \IteratorAggregate, \Countable
+{
+ protected $storage;
+
+ private $flashName;
+ private $attributeName;
+ private $data = array();
+ private $hasBeenStarted;
+
+ /**
+ * @param SessionStorageInterface $storage A SessionStorageInterface instance
+ * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag)
+ * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag)
+ */
+ public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null)
+ {
+ $this->storage = $storage ?: new NativeSessionStorage();
+
+ $attributes = $attributes ?: new AttributeBag();
+ $this->attributeName = $attributes->getName();
+ $this->registerBag($attributes);
+
+ $flashes = $flashes ?: new FlashBag();
+ $this->flashName = $flashes->getName();
+ $this->registerBag($flashes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ return $this->storage->start();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ return $this->getAttributeBag()->has($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name, $default = null)
+ {
+ return $this->getAttributeBag()->get($name, $default);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($name, $value)
+ {
+ $this->getAttributeBag()->set($name, $value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ return $this->getAttributeBag()->all();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $attributes)
+ {
+ $this->getAttributeBag()->replace($attributes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($name)
+ {
+ return $this->getAttributeBag()->remove($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ $this->getAttributeBag()->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isStarted()
+ {
+ return $this->storage->isStarted();
+ }
+
+ /**
+ * Returns an iterator for attributes.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->getAttributeBag()->all());
+ }
+
+ /**
+ * Returns the number of attributes.
+ *
+ * @return int The number of attributes
+ */
+ public function count()
+ {
+ return count($this->getAttributeBag()->all());
+ }
+
+ /**
+ * @return bool
+ *
+ * @internal
+ */
+ public function hasBeenStarted()
+ {
+ return $this->hasBeenStarted;
+ }
+
+ /**
+ * @return bool
+ *
+ * @internal
+ */
+ public function isEmpty()
+ {
+ foreach ($this->data as &$data) {
+ if (!empty($data)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidate($lifetime = null)
+ {
+ $this->storage->clear();
+
+ return $this->migrate(true, $lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function migrate($destroy = false, $lifetime = null)
+ {
+ return $this->storage->regenerate($destroy, $lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save()
+ {
+ $this->storage->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getId()
+ {
+ return $this->storage->getId();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setId($id)
+ {
+ $this->storage->setId($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->storage->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name)
+ {
+ $this->storage->setName($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataBag()
+ {
+ return $this->storage->getMetadataBag();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerBag(SessionBagInterface $bag)
+ {
+ $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->hasBeenStarted));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBag($name)
+ {
+ return $this->storage->getBag($name)->getBag();
+ }
+
+ /**
+ * Gets the flashbag interface.
+ *
+ * @return FlashBagInterface
+ */
+ public function getFlashBag()
+ {
+ return $this->getBag($this->flashName);
+ }
+
+ /**
+ * Gets the attributebag interface.
+ *
+ * Note that this method was added to help with IDE autocompletion.
+ *
+ * @return AttributeBagInterface
+ */
+ private function getAttributeBag()
+ {
+ return $this->getBag($this->attributeName);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/assets/php/vendor/symfony/http-foundation/Session/SessionBagInterface.php
new file mode 100644
index 0000000..8e37d06
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/SessionBagInterface.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+/**
+ * Session Bag store.
+ *
+ * @author Drak
+ */
+interface SessionBagInterface
+{
+ /**
+ * Gets this bag's name.
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Initializes the Bag.
+ */
+ public function initialize(array &$array);
+
+ /**
+ * Gets the storage key for this bag.
+ *
+ * @return string
+ */
+ public function getStorageKey();
+
+ /**
+ * Clears out data from bag.
+ *
+ * @return mixed Whatever data was contained
+ */
+ public function clear();
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/assets/php/vendor/symfony/http-foundation/Session/SessionBagProxy.php
new file mode 100644
index 0000000..307836d
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/SessionBagProxy.php
@@ -0,0 +1,82 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+final class SessionBagProxy implements SessionBagInterface
+{
+ private $bag;
+ private $data;
+ private $hasBeenStarted;
+
+ public function __construct(SessionBagInterface $bag, array &$data, &$hasBeenStarted)
+ {
+ $this->bag = $bag;
+ $this->data = &$data;
+ $this->hasBeenStarted = &$hasBeenStarted;
+ }
+
+ /**
+ * @return SessionBagInterface
+ */
+ public function getBag()
+ {
+ return $this->bag;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return empty($this->data[$this->bag->getStorageKey()]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->bag->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$array)
+ {
+ $this->hasBeenStarted = true;
+ $this->data[$this->bag->getStorageKey()] = &$array;
+
+ $this->bag->initialize($array);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->bag->getStorageKey();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->bag->clear();
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/SessionInterface.php b/assets/php/vendor/symfony/http-foundation/Session/SessionInterface.php
new file mode 100644
index 0000000..95fca85
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/SessionInterface.php
@@ -0,0 +1,180 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
+
+/**
+ * Interface for the session.
+ *
+ * @author Drak
+ */
+interface SessionInterface
+{
+ /**
+ * Starts the session storage.
+ *
+ * @return bool True if session started
+ *
+ * @throws \RuntimeException if session fails to start
+ */
+ public function start();
+
+ /**
+ * Returns the session ID.
+ *
+ * @return string The session ID
+ */
+ public function getId();
+
+ /**
+ * Sets the session ID.
+ *
+ * @param string $id
+ */
+ public function setId($id);
+
+ /**
+ * Returns the session name.
+ *
+ * @return mixed The session name
+ */
+ public function getName();
+
+ /**
+ * Sets the session name.
+ *
+ * @param string $name
+ */
+ public function setName($name);
+
+ /**
+ * Invalidates the current session.
+ *
+ * Clears all session attributes and flashes and regenerates the
+ * session and deletes the old session from persistence.
+ *
+ * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+ * will leave the system settings unchanged, 0 sets the cookie
+ * to expire with browser session. Time is in seconds, and is
+ * not a Unix timestamp.
+ *
+ * @return bool True if session invalidated, false if error
+ */
+ public function invalidate($lifetime = null);
+
+ /**
+ * Migrates the current session to a new session id while maintaining all
+ * session attributes.
+ *
+ * @param bool $destroy Whether to delete the old session or leave it to garbage collection
+ * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+ * will leave the system settings unchanged, 0 sets the cookie
+ * to expire with browser session. Time is in seconds, and is
+ * not a Unix timestamp.
+ *
+ * @return bool True if session migrated, false if error
+ */
+ public function migrate($destroy = false, $lifetime = null);
+
+ /**
+ * Force the session to be saved and closed.
+ *
+ * This method is generally not required for real sessions as
+ * the session will be automatically saved at the end of
+ * code execution.
+ */
+ public function save();
+
+ /**
+ * Checks if an attribute is defined.
+ *
+ * @param string $name The attribute name
+ *
+ * @return bool true if the attribute is defined, false otherwise
+ */
+ public function has($name);
+
+ /**
+ * Returns an attribute.
+ *
+ * @param string $name The attribute name
+ * @param mixed $default The default value if not found
+ *
+ * @return mixed
+ */
+ public function get($name, $default = null);
+
+ /**
+ * Sets an attribute.
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function set($name, $value);
+
+ /**
+ * Returns attributes.
+ *
+ * @return array Attributes
+ */
+ public function all();
+
+ /**
+ * Sets attributes.
+ *
+ * @param array $attributes Attributes
+ */
+ public function replace(array $attributes);
+
+ /**
+ * Removes an attribute.
+ *
+ * @param string $name
+ *
+ * @return mixed The removed value or null when it does not exist
+ */
+ public function remove($name);
+
+ /**
+ * Clears all attributes.
+ */
+ public function clear();
+
+ /**
+ * Checks if the session was started.
+ *
+ * @return bool
+ */
+ public function isStarted();
+
+ /**
+ * Registers a SessionBagInterface with the session.
+ */
+ public function registerBag(SessionBagInterface $bag);
+
+ /**
+ * Gets a bag instance by name.
+ *
+ * @param string $name
+ *
+ * @return SessionBagInterface
+ */
+ public function getBag($name);
+
+ /**
+ * Gets session meta.
+ *
+ * @return MetadataBag
+ */
+ public function getMetadataBag();
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
new file mode 100644
index 0000000..6ae1355
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
@@ -0,0 +1,168 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * This abstract session handler provides a generic implementation
+ * of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
+ * enabling strict and lazy session handling.
+ *
+ * @author Nicolas Grekas
+ */
+abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+ private $sessionName;
+ private $prefetchId;
+ private $prefetchData;
+ private $newSessionId;
+ private $igbinaryEmptyData;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ $this->sessionName = $sessionName;
+ if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) {
+ header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire')));
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $sessionId
+ *
+ * @return string
+ */
+ abstract protected function doRead($sessionId);
+
+ /**
+ * @param string $sessionId
+ * @param string $data
+ *
+ * @return bool
+ */
+ abstract protected function doWrite($sessionId, $data);
+
+ /**
+ * @param string $sessionId
+ *
+ * @return bool
+ */
+ abstract protected function doDestroy($sessionId);
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateId($sessionId)
+ {
+ $this->prefetchData = $this->read($sessionId);
+ $this->prefetchId = $sessionId;
+
+ return '' !== $this->prefetchData;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ if (null !== $this->prefetchId) {
+ $prefetchId = $this->prefetchId;
+ $prefetchData = $this->prefetchData;
+ $this->prefetchId = $this->prefetchData = null;
+
+ if ($prefetchId === $sessionId || '' === $prefetchData) {
+ $this->newSessionId = '' === $prefetchData ? $sessionId : null;
+
+ return $prefetchData;
+ }
+ }
+
+ $data = $this->doRead($sessionId);
+ $this->newSessionId = '' === $data ? $sessionId : null;
+ if (\PHP_VERSION_ID < 70000) {
+ $this->prefetchData = $data;
+ }
+
+ return $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ if (\PHP_VERSION_ID < 70000 && $this->prefetchData) {
+ $readData = $this->prefetchData;
+ $this->prefetchData = null;
+
+ if ($readData === $data) {
+ return $this->updateTimestamp($sessionId, $data);
+ }
+ }
+ if (null === $this->igbinaryEmptyData) {
+ // see https://github.com/igbinary/igbinary/issues/146
+ $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize(array()) : '';
+ }
+ if ('' === $data || $this->igbinaryEmptyData === $data) {
+ return $this->destroy($sessionId);
+ }
+ $this->newSessionId = null;
+
+ return $this->doWrite($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ if (\PHP_VERSION_ID < 70000) {
+ $this->prefetchData = null;
+ }
+ if (!headers_sent() && ini_get('session.use_cookies')) {
+ if (!$this->sessionName) {
+ throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', get_class($this)));
+ }
+ $sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
+ $sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
+ $sessionCookieFound = false;
+ $otherCookies = array();
+ foreach (headers_list() as $h) {
+ if (0 !== stripos($h, 'Set-Cookie:')) {
+ continue;
+ }
+ if (11 === strpos($h, $sessionCookie, 11)) {
+ $sessionCookieFound = true;
+
+ if (11 !== strpos($h, $sessionCookieWithId, 11)) {
+ $otherCookies[] = $h;
+ }
+ } else {
+ $otherCookies[] = $h;
+ }
+ }
+ if ($sessionCookieFound) {
+ header_remove('Set-Cookie');
+ foreach ($otherCookies as $h) {
+ header($h, false);
+ }
+ } else {
+ setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
+ }
+ }
+
+ return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php
new file mode 100644
index 0000000..90726be
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php
@@ -0,0 +1,120 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+@trigger_error(sprintf('The class %s is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.', MemcacheSessionHandler::class), E_USER_DEPRECATED);
+
+/**
+ * @author Drak
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.
+ */
+class MemcacheSessionHandler implements \SessionHandlerInterface
+{
+ private $memcache;
+
+ /**
+ * @var int Time to live in seconds
+ */
+ private $ttl;
+
+ /**
+ * @var string Key prefix for shared environments
+ */
+ private $prefix;
+
+ /**
+ * Constructor.
+ *
+ * List of available options:
+ * * prefix: The prefix to use for the memcache keys in order to avoid collision
+ * * expiretime: The time to live in seconds
+ *
+ * @param \Memcache $memcache A \Memcache instance
+ * @param array $options An associative array of Memcache options
+ *
+ * @throws \InvalidArgumentException When unsupported options are passed
+ */
+ public function __construct(\Memcache $memcache, array $options = array())
+ {
+ if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The following options are not supported "%s"', implode(', ', $diff)
+ ));
+ }
+
+ $this->memcache = $memcache;
+ $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
+ $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ return $this->memcache->get($this->prefix.$sessionId) ?: '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ $this->memcache->delete($this->prefix.$sessionId);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ // not required here because memcache will auto expire the records anyhow.
+ return true;
+ }
+
+ /**
+ * Return a Memcache instance.
+ *
+ * @return \Memcache
+ */
+ protected function getMemcache()
+ {
+ return $this->memcache;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
new file mode 100644
index 0000000..dd37eae
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -0,0 +1,124 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Memcached based session storage handler based on the Memcached class
+ * provided by the PHP memcached extension.
+ *
+ * @see http://php.net/memcached
+ *
+ * @author Drak
+ */
+class MemcachedSessionHandler extends AbstractSessionHandler
+{
+ private $memcached;
+
+ /**
+ * @var int Time to live in seconds
+ */
+ private $ttl;
+
+ /**
+ * @var string Key prefix for shared environments
+ */
+ private $prefix;
+
+ /**
+ * Constructor.
+ *
+ * List of available options:
+ * * prefix: The prefix to use for the memcached keys in order to avoid collision
+ * * expiretime: The time to live in seconds.
+ *
+ * @param \Memcached $memcached A \Memcached instance
+ * @param array $options An associative array of Memcached options
+ *
+ * @throws \InvalidArgumentException When unsupported options are passed
+ */
+ public function __construct(\Memcached $memcached, array $options = array())
+ {
+ $this->memcached = $memcached;
+
+ if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The following options are not supported "%s"', implode(', ', $diff)
+ ));
+ }
+
+ $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
+ $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ return $this->memcached->get($this->prefix.$sessionId) ?: '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ $result = $this->memcached->delete($this->prefix.$sessionId);
+
+ return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ // not required here because memcached will auto expire the records anyhow.
+ return true;
+ }
+
+ /**
+ * Return a Memcached instance.
+ *
+ * @return \Memcached
+ */
+ protected function getMemcached()
+ {
+ return $this->memcached;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
new file mode 100644
index 0000000..7d3fa21
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
@@ -0,0 +1,255 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Session handler using the mongodb/mongodb package and MongoDB driver extension.
+ *
+ * @author Markus Bachmann
+ *
+ * @see https://packagist.org/packages/mongodb/mongodb
+ * @see http://php.net/manual/en/set.mongodb.php
+ */
+class MongoDbSessionHandler extends AbstractSessionHandler
+{
+ private $mongo;
+
+ /**
+ * @var \MongoCollection
+ */
+ private $collection;
+
+ /**
+ * @var array
+ */
+ private $options;
+
+ /**
+ * Constructor.
+ *
+ * List of available options:
+ * * database: The name of the database [required]
+ * * collection: The name of the collection [required]
+ * * id_field: The field name for storing the session id [default: _id]
+ * * data_field: The field name for storing the session data [default: data]
+ * * time_field: The field name for storing the timestamp [default: time]
+ * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
+ *
+ * It is strongly recommended to put an index on the `expiry_field` for
+ * garbage-collection. Alternatively it's possible to automatically expire
+ * the sessions in the database as described below:
+ *
+ * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions
+ * automatically. Such an index can for example look like this:
+ *
+ * db..ensureIndex(
+ * { "": 1 },
+ * { "expireAfterSeconds": 0 }
+ * )
+ *
+ * More details on: http://docs.mongodb.org/manual/tutorial/expire-data/
+ *
+ * If you use such an index, you can drop `gc_probability` to 0 since
+ * no garbage-collection is required.
+ *
+ * @param \MongoDB\Client $mongo A MongoDB\Client instance
+ * @param array $options An associative array of field options
+ *
+ * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
+ * @throws \InvalidArgumentException When "database" or "collection" not provided
+ */
+ public function __construct($mongo, array $options)
+ {
+ if ($mongo instanceof \MongoClient || $mongo instanceof \Mongo) {
+ @trigger_error(sprintf('Using %s with the legacy mongo extension is deprecated as of 3.4 and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead.', __CLASS__), E_USER_DEPRECATED);
+ }
+
+ if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
+ throw new \InvalidArgumentException('MongoClient or Mongo instance required');
+ }
+
+ if (!isset($options['database']) || !isset($options['collection'])) {
+ throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler');
+ }
+
+ $this->mongo = $mongo;
+
+ $this->options = array_merge(array(
+ 'id_field' => '_id',
+ 'data_field' => 'data',
+ 'time_field' => 'time',
+ 'expiry_field' => 'expires_at',
+ ), $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
+
+ $this->getCollection()->$methodName(array(
+ $this->options['id_field'] => $sessionId,
+ ));
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteMany' : 'remove';
+
+ $this->getCollection()->$methodName(array(
+ $this->options['expiry_field'] => array('$lt' => $this->createDateTime()),
+ ));
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
+
+ $fields = array(
+ $this->options['time_field'] => $this->createDateTime(),
+ $this->options['expiry_field'] => $expiry,
+ );
+
+ $options = array('upsert' => true);
+
+ if ($this->mongo instanceof \MongoDB\Client) {
+ $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
+ } else {
+ $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY);
+ $options['multiple'] = false;
+ }
+
+ $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update';
+
+ $this->getCollection()->$methodName(
+ array($this->options['id_field'] => $sessionId),
+ array('$set' => $fields),
+ $options
+ );
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
+
+ if ($this->mongo instanceof \MongoDB\Client) {
+ $methodName = 'updateOne';
+ $options = array();
+ } else {
+ $methodName = 'update';
+ $options = array('multiple' => false);
+ }
+
+ $this->getCollection()->$methodName(
+ array($this->options['id_field'] => $sessionId),
+ array('$set' => array(
+ $this->options['time_field'] => $this->createDateTime(),
+ $this->options['expiry_field'] => $expiry,
+ )),
+ $options
+ );
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ $dbData = $this->getCollection()->findOne(array(
+ $this->options['id_field'] => $sessionId,
+ $this->options['expiry_field'] => array('$gte' => $this->createDateTime()),
+ ));
+
+ if (null === $dbData) {
+ return '';
+ }
+
+ if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) {
+ return $dbData[$this->options['data_field']]->getData();
+ }
+
+ return $dbData[$this->options['data_field']]->bin;
+ }
+
+ /**
+ * Return a "MongoCollection" instance.
+ *
+ * @return \MongoCollection
+ */
+ private function getCollection()
+ {
+ if (null === $this->collection) {
+ $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
+ }
+
+ return $this->collection;
+ }
+
+ /**
+ * Return a Mongo instance.
+ *
+ * @return \Mongo|\MongoClient|\MongoDB\Client
+ */
+ protected function getMongo()
+ {
+ return $this->mongo;
+ }
+
+ /**
+ * Create a date object using the class appropriate for the current mongo connection.
+ *
+ * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime
+ *
+ * @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now.
+ *
+ * @return \MongoDate|\MongoDB\BSON\UTCDateTime
+ */
+ private function createDateTime($seconds = null)
+ {
+ if (null === $seconds) {
+ $seconds = time();
+ }
+
+ if ($this->mongo instanceof \MongoDB\Client) {
+ return new \MongoDB\BSON\UTCDateTime($seconds * 1000);
+ }
+
+ return new \MongoDate($seconds);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
new file mode 100644
index 0000000..4e9704b
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Native session handler using PHP's built in file storage.
+ *
+ * @author Drak
+ */
+class NativeFileSessionHandler extends NativeSessionHandler
+{
+ /**
+ * @param string $savePath Path of directory to save session files
+ * Default null will leave setting as defined by PHP.
+ * '/path', 'N;/path', or 'N;octal-mode;/path
+ *
+ * @see http://php.net/session.configuration.php#ini.session.save-path for further details.
+ *
+ * @throws \InvalidArgumentException On invalid $savePath
+ * @throws \RuntimeException When failing to create the save directory
+ */
+ public function __construct($savePath = null)
+ {
+ if (null === $savePath) {
+ $savePath = ini_get('session.save_path');
+ }
+
+ $baseDir = $savePath;
+
+ if ($count = substr_count($savePath, ';')) {
+ if ($count > 2) {
+ throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath));
+ }
+
+ // characters after last ';' are the path
+ $baseDir = ltrim(strrchr($savePath, ';'), ';');
+ }
+
+ if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
+ throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir));
+ }
+
+ ini_set('session.save_path', $savePath);
+ ini_set('session.save_handler', 'files');
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php
new file mode 100644
index 0000000..9be4528
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead.
+ * @see http://php.net/sessionhandler
+ */
+class NativeSessionHandler extends \SessionHandler
+{
+ public function __construct()
+ {
+ @trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
new file mode 100644
index 0000000..8d19315
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Can be used in unit testing or in a situations where persisted sessions are not desired.
+ *
+ * @author Drak
+ */
+class NullSessionHandler extends AbstractSessionHandler
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateId($sessionId)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ return '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return true;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
new file mode 100644
index 0000000..c8737be
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
@@ -0,0 +1,910 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Session handler using a PDO connection to read and write data.
+ *
+ * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements
+ * different locking strategies to handle concurrent access to the same session.
+ * Locking is necessary to prevent loss of data due to race conditions and to keep
+ * the session data consistent between read() and write(). With locking, requests
+ * for the same session will wait until the other one finished writing. For this
+ * reason it's best practice to close a session as early as possible to improve
+ * concurrency. PHPs internal files session handler also implements locking.
+ *
+ * Attention: Since SQLite does not support row level locks but locks the whole database,
+ * it means only one session can be accessed at a time. Even different sessions would wait
+ * for another to finish. So saving session in SQLite should only be considered for
+ * development or prototypes.
+ *
+ * Session data is a binary string that can contain non-printable characters like the null byte.
+ * For this reason it must be saved in a binary column in the database like BLOB in MySQL.
+ * Saving it in a character column could corrupt the data. You can use createTable()
+ * to initialize a correctly defined table.
+ *
+ * @see http://php.net/sessionhandlerinterface
+ *
+ * @author Fabien Potencier
+ * @author Michael Williams
+ * @author Tobias Schultze
+ */
+class PdoSessionHandler extends AbstractSessionHandler
+{
+ /**
+ * No locking is done. This means sessions are prone to loss of data due to
+ * race conditions of concurrent requests to the same session. The last session
+ * write will win in this case. It might be useful when you implement your own
+ * logic to deal with this like an optimistic approach.
+ */
+ const LOCK_NONE = 0;
+
+ /**
+ * Creates an application-level lock on a session. The disadvantage is that the
+ * lock is not enforced by the database and thus other, unaware parts of the
+ * application could still concurrently modify the session. The advantage is it
+ * does not require a transaction.
+ * This mode is not available for SQLite and not yet implemented for oci and sqlsrv.
+ */
+ const LOCK_ADVISORY = 1;
+
+ /**
+ * Issues a real row lock. Since it uses a transaction between opening and
+ * closing a session, you have to be careful when you use same database connection
+ * that you also use for your application logic. This mode is the default because
+ * it's the only reliable solution across DBMSs.
+ */
+ const LOCK_TRANSACTIONAL = 2;
+
+ /**
+ * @var \PDO|null PDO instance or null when not connected yet
+ */
+ private $pdo;
+
+ /**
+ * @var string|null|false DSN string or null for session.save_path or false when lazy connection disabled
+ */
+ private $dsn = false;
+
+ /**
+ * @var string Database driver
+ */
+ private $driver;
+
+ /**
+ * @var string Table name
+ */
+ private $table = 'sessions';
+
+ /**
+ * @var string Column for session id
+ */
+ private $idCol = 'sess_id';
+
+ /**
+ * @var string Column for session data
+ */
+ private $dataCol = 'sess_data';
+
+ /**
+ * @var string Column for lifetime
+ */
+ private $lifetimeCol = 'sess_lifetime';
+
+ /**
+ * @var string Column for timestamp
+ */
+ private $timeCol = 'sess_time';
+
+ /**
+ * @var string Username when lazy-connect
+ */
+ private $username = '';
+
+ /**
+ * @var string Password when lazy-connect
+ */
+ private $password = '';
+
+ /**
+ * @var array Connection options when lazy-connect
+ */
+ private $connectionOptions = array();
+
+ /**
+ * @var int The strategy for locking, see constants
+ */
+ private $lockMode = self::LOCK_TRANSACTIONAL;
+
+ /**
+ * It's an array to support multiple reads before closing which is manual, non-standard usage.
+ *
+ * @var \PDOStatement[] An array of statements to release advisory locks
+ */
+ private $unlockStatements = array();
+
+ /**
+ * @var bool True when the current session exists but expired according to session.gc_maxlifetime
+ */
+ private $sessionExpired = false;
+
+ /**
+ * @var bool Whether a transaction is active
+ */
+ private $inTransaction = false;
+
+ /**
+ * @var bool Whether gc() has been called
+ */
+ private $gcCalled = false;
+
+ /**
+ * You can either pass an existing database connection as PDO instance or
+ * pass a DSN string that will be used to lazy-connect to the database
+ * when the session is actually used. Furthermore it's possible to pass null
+ * which will then use the session.save_path ini setting as PDO DSN parameter.
+ *
+ * List of available options:
+ * * db_table: The name of the table [default: sessions]
+ * * db_id_col: The column where to store the session id [default: sess_id]
+ * * db_data_col: The column where to store the session data [default: sess_data]
+ * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime]
+ * * db_time_col: The column where to store the timestamp [default: sess_time]
+ * * db_username: The username when lazy-connect [default: '']
+ * * db_password: The password when lazy-connect [default: '']
+ * * db_connection_options: An array of driver-specific connection options [default: array()]
+ * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
+ *
+ * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null
+ * @param array $options An associative array of options
+ *
+ * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
+ */
+ public function __construct($pdoOrDsn = null, array $options = array())
+ {
+ if ($pdoOrDsn instanceof \PDO) {
+ if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
+ throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
+ }
+
+ $this->pdo = $pdoOrDsn;
+ $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ } elseif (is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) {
+ $this->dsn = $this->buildDsnFromUrl($pdoOrDsn);
+ } else {
+ $this->dsn = $pdoOrDsn;
+ }
+
+ $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
+ $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
+ $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
+ $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
+ $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
+ $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
+ $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
+ $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
+ $this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode;
+ }
+
+ /**
+ * Creates the table to store sessions which can be called once for setup.
+ *
+ * Session ID is saved in a column of maximum length 128 because that is enough even
+ * for a 512 bit configured session.hash_function like Whirlpool. Session data is
+ * saved in a BLOB. One could also use a shorter inlined varbinary column
+ * if one was sure the data fits into it.
+ *
+ * @throws \PDOException When the table already exists
+ * @throws \DomainException When an unsupported PDO driver is used
+ */
+ public function createTable()
+ {
+ // connect if we are not yet
+ $this->getConnection();
+
+ switch ($this->driver) {
+ case 'mysql':
+ // We use varbinary for the ID column because it prevents unwanted conversions:
+ // - character set conversions between server and client
+ // - trailing space removal
+ // - case-insensitivity
+ // - language processing like é == e
+ $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
+ break;
+ case 'sqlite':
+ $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'pgsql':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'oci':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'sqlsrv':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+ break;
+ default:
+ throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
+ }
+
+ try {
+ $this->pdo->exec($sql);
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+ }
+
+ /**
+ * Returns true when the current session exists but expired according to session.gc_maxlifetime.
+ *
+ * Can be used to distinguish between a new session and one that expired due to inactivity.
+ *
+ * @return bool Whether current session expired
+ */
+ public function isSessionExpired()
+ {
+ return $this->sessionExpired;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ $this->sessionExpired = false;
+
+ if (null === $this->pdo) {
+ $this->connect($this->dsn ?: $savePath);
+ }
+
+ return parent::open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ try {
+ return parent::read($sessionId);
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process.
+ // This way, pruning expired sessions does not block them from being started while the current session is used.
+ $this->gcCalled = true;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ // delete the record associated with this id
+ $sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
+
+ try {
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $stmt->execute();
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ $maxlifetime = (int) ini_get('session.gc_maxlifetime');
+
+ try {
+ // We use a single MERGE SQL query when supported by the database.
+ $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime);
+ if (null !== $mergeStmt) {
+ $mergeStmt->execute();
+
+ return true;
+ }
+
+ $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime);
+ $updateStmt->execute();
+
+ // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in
+ // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior).
+ // We can just catch such an error and re-execute the update. This is similar to a serializable
+ // transaction with retry logic on serialization failures but without the overhead and without possible
+ // false positives due to longer gap locking.
+ if (!$updateStmt->rowCount()) {
+ try {
+ $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime);
+ $insertStmt->execute();
+ } catch (\PDOException $e) {
+ // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
+ if (0 === strpos($e->getCode(), '23')) {
+ $updateStmt->execute();
+ } else {
+ throw $e;
+ }
+ }
+ }
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ $maxlifetime = (int) ini_get('session.gc_maxlifetime');
+
+ try {
+ $updateStmt = $this->pdo->prepare(
+ "UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"
+ );
+ $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $updateStmt->execute();
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->commit();
+
+ while ($unlockStmt = array_shift($this->unlockStatements)) {
+ $unlockStmt->execute();
+ }
+
+ if ($this->gcCalled) {
+ $this->gcCalled = false;
+
+ // delete the session records that have expired
+ if ('mysql' === $this->driver) {
+ $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time";
+ } else {
+ $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time - $this->timeCol";
+ }
+
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $stmt->execute();
+ }
+
+ if (false !== $this->dsn) {
+ $this->pdo = null; // only close lazy-connection
+ }
+
+ return true;
+ }
+
+ /**
+ * Lazy-connects to the database.
+ *
+ * @param string $dsn DSN string
+ */
+ private function connect($dsn)
+ {
+ $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions);
+ $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ }
+
+ /**
+ * Builds a PDO DSN from a URL-like connection string.
+ *
+ * @param string $dsnOrUrl
+ *
+ * @return string
+ *
+ * @todo implement missing support for oci DSN (which look totally different from other PDO ones)
+ */
+ private function buildDsnFromUrl($dsnOrUrl)
+ {
+ // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
+ $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl);
+
+ $params = parse_url($url);
+
+ if (false === $params) {
+ return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already.
+ }
+
+ $params = array_map('rawurldecode', $params);
+
+ // Override the default username and password. Values passed through options will still win over these in the constructor.
+ if (isset($params['user'])) {
+ $this->username = $params['user'];
+ }
+
+ if (isset($params['pass'])) {
+ $this->password = $params['pass'];
+ }
+
+ if (!isset($params['scheme'])) {
+ throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler');
+ }
+
+ $driverAliasMap = array(
+ 'mssql' => 'sqlsrv',
+ 'mysql2' => 'mysql', // Amazon RDS, for some weird reason
+ 'postgres' => 'pgsql',
+ 'postgresql' => 'pgsql',
+ 'sqlite3' => 'sqlite',
+ );
+
+ $driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme'];
+
+ // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here.
+ if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) {
+ $driver = substr($driver, 4);
+ }
+
+ switch ($driver) {
+ case 'mysql':
+ case 'pgsql':
+ $dsn = $driver.':';
+
+ if (isset($params['host']) && '' !== $params['host']) {
+ $dsn .= 'host='.$params['host'].';';
+ }
+
+ if (isset($params['port']) && '' !== $params['port']) {
+ $dsn .= 'port='.$params['port'].';';
+ }
+
+ if (isset($params['path'])) {
+ $dbName = substr($params['path'], 1); // Remove the leading slash
+ $dsn .= 'dbname='.$dbName.';';
+ }
+
+ return $dsn;
+
+ case 'sqlite':
+ return 'sqlite:'.substr($params['path'], 1);
+
+ case 'sqlsrv':
+ $dsn = 'sqlsrv:server=';
+
+ if (isset($params['host'])) {
+ $dsn .= $params['host'];
+ }
+
+ if (isset($params['port']) && '' !== $params['port']) {
+ $dsn .= ','.$params['port'];
+ }
+
+ if (isset($params['path'])) {
+ $dbName = substr($params['path'], 1); // Remove the leading slash
+ $dsn .= ';Database='.$dbName;
+ }
+
+ return $dsn;
+
+ default:
+ throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
+ }
+ }
+
+ /**
+ * Helper method to begin a transaction.
+ *
+ * Since SQLite does not support row level locks, we have to acquire a reserved lock
+ * on the database immediately. Because of https://bugs.php.net/42766 we have to create
+ * such a transaction manually which also means we cannot use PDO::commit or
+ * PDO::rollback or PDO::inTransaction for SQLite.
+ *
+ * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions
+ * due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ .
+ * So we change it to READ COMMITTED.
+ */
+ private function beginTransaction()
+ {
+ if (!$this->inTransaction) {
+ if ('sqlite' === $this->driver) {
+ $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION');
+ } else {
+ if ('mysql' === $this->driver) {
+ $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+ }
+ $this->pdo->beginTransaction();
+ }
+ $this->inTransaction = true;
+ }
+ }
+
+ /**
+ * Helper method to commit a transaction.
+ */
+ private function commit()
+ {
+ if ($this->inTransaction) {
+ try {
+ // commit read-write transaction which also releases the lock
+ if ('sqlite' === $this->driver) {
+ $this->pdo->exec('COMMIT');
+ } else {
+ $this->pdo->commit();
+ }
+ $this->inTransaction = false;
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * Helper method to rollback a transaction.
+ */
+ private function rollback()
+ {
+ // We only need to rollback if we are in a transaction. Otherwise the resulting
+ // error would hide the real problem why rollback was called. We might not be
+ // in a transaction when not using the transactional locking behavior or when
+ // two callbacks (e.g. destroy and write) are invoked that both fail.
+ if ($this->inTransaction) {
+ if ('sqlite' === $this->driver) {
+ $this->pdo->exec('ROLLBACK');
+ } else {
+ $this->pdo->rollBack();
+ }
+ $this->inTransaction = false;
+ }
+ }
+
+ /**
+ * Reads the session data in respect to the different locking strategies.
+ *
+ * We need to make sure we do not return session data that is already considered garbage according
+ * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes.
+ *
+ * @param string $sessionId Session ID
+ *
+ * @return string The session data
+ */
+ protected function doRead($sessionId)
+ {
+ if (self::LOCK_ADVISORY === $this->lockMode) {
+ $this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
+ }
+
+ $selectSql = $this->getSelectSql();
+ $selectStmt = $this->pdo->prepare($selectSql);
+ $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+
+ do {
+ $selectStmt->execute();
+ $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
+
+ if ($sessionRows) {
+ if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
+ $this->sessionExpired = true;
+
+ return '';
+ }
+
+ return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
+ }
+
+ if (!ini_get('session.use_strict_mode') && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
+ // In strict mode, session fixation is not possible: new sessions always start with a unique
+ // random id, so that concurrency is not possible and this code path can be skipped.
+ // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
+ // until other connections to the session are committed.
+ try {
+ $insertStmt = $this->getInsertStatement($sessionId, '', 0);
+ $insertStmt->execute();
+ } catch (\PDOException $e) {
+ // Catch duplicate key error because other connection created the session already.
+ // It would only not be the case when the other connection destroyed the session.
+ if (0 === strpos($e->getCode(), '23')) {
+ // Retrieve finished session data written by concurrent connection by restarting the loop.
+ // We have to start a new transaction as a failed query will mark the current transaction as
+ // aborted in PostgreSQL and disallow further queries within it.
+ $this->rollback();
+ $this->beginTransaction();
+ continue;
+ }
+
+ throw $e;
+ }
+ }
+
+ return '';
+ } while (true);
+ }
+
+ /**
+ * Executes an application-level lock on the database.
+ *
+ * @param string $sessionId Session ID
+ *
+ * @return \PDOStatement The statement that needs to be executed later to release the lock
+ *
+ * @throws \DomainException When an unsupported PDO driver is used
+ *
+ * @todo implement missing advisory locks
+ * - for oci using DBMS_LOCK.REQUEST
+ * - for sqlsrv using sp_getapplock with LockOwner = Session
+ */
+ private function doAdvisoryLock($sessionId)
+ {
+ switch ($this->driver) {
+ case 'mysql':
+ // should we handle the return value? 0 on timeout, null on error
+ // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout
+ $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)');
+ $stmt->bindValue(':key', $sessionId, \PDO::PARAM_STR);
+ $stmt->execute();
+
+ $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)');
+ $releaseStmt->bindValue(':key', $sessionId, \PDO::PARAM_STR);
+
+ return $releaseStmt;
+ case 'pgsql':
+ // Obtaining an exclusive session level advisory lock requires an integer key.
+ // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters.
+ // So we cannot just use hexdec().
+ if (4 === \PHP_INT_SIZE) {
+ $sessionInt1 = $this->convertStringToInt($sessionId);
+ $sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4));
+
+ $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)');
+ $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
+ $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
+ $stmt->execute();
+
+ $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)');
+ $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
+ $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
+ } else {
+ $sessionBigInt = $this->convertStringToInt($sessionId);
+
+ $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)');
+ $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
+ $stmt->execute();
+
+ $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)');
+ $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
+ }
+
+ return $releaseStmt;
+ case 'sqlite':
+ throw new \DomainException('SQLite does not support advisory locks.');
+ default:
+ throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
+ }
+ }
+
+ /**
+ * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer.
+ *
+ * Keep in mind, PHP integers are signed.
+ *
+ * @param string $string
+ *
+ * @return int
+ */
+ private function convertStringToInt($string)
+ {
+ if (4 === \PHP_INT_SIZE) {
+ return (ord($string[3]) << 24) + (ord($string[2]) << 16) + (ord($string[1]) << 8) + ord($string[0]);
+ }
+
+ $int1 = (ord($string[7]) << 24) + (ord($string[6]) << 16) + (ord($string[5]) << 8) + ord($string[4]);
+ $int2 = (ord($string[3]) << 24) + (ord($string[2]) << 16) + (ord($string[1]) << 8) + ord($string[0]);
+
+ return $int2 + ($int1 << 32);
+ }
+
+ /**
+ * Return a locking or nonlocking SQL query to read session information.
+ *
+ * @return string The SQL string
+ *
+ * @throws \DomainException When an unsupported PDO driver is used
+ */
+ private function getSelectSql()
+ {
+ if (self::LOCK_TRANSACTIONAL === $this->lockMode) {
+ $this->beginTransaction();
+
+ switch ($this->driver) {
+ case 'mysql':
+ case 'oci':
+ case 'pgsql':
+ return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE";
+ case 'sqlsrv':
+ return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id";
+ case 'sqlite':
+ // we already locked when starting transaction
+ break;
+ default:
+ throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
+ }
+ }
+
+ return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id";
+ }
+
+ /**
+ * Returns an insert statement supported by the database for writing session data.
+ *
+ * @param string $sessionId Session ID
+ * @param string $sessionData Encoded session data
+ * @param int $maxlifetime session.gc_maxlifetime
+ *
+ * @return \PDOStatement The insert statement
+ */
+ private function getInsertStatement($sessionId, $sessionData, $maxlifetime)
+ {
+ switch ($this->driver) {
+ case 'oci':
+ $data = fopen('php://memory', 'r+');
+ fwrite($data, $sessionData);
+ rewind($data);
+ $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :lifetime, :time) RETURNING $this->dataCol into :data";
+ break;
+ default:
+ $data = $sessionData;
+ $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
+ break;
+ }
+
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+ $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+
+ return $stmt;
+ }
+
+ /**
+ * Returns an update statement supported by the database for writing session data.
+ *
+ * @param string $sessionId Session ID
+ * @param string $sessionData Encoded session data
+ * @param int $maxlifetime session.gc_maxlifetime
+ *
+ * @return \PDOStatement The update statement
+ */
+ private function getUpdateStatement($sessionId, $sessionData, $maxlifetime)
+ {
+ switch ($this->driver) {
+ case 'oci':
+ $data = fopen('php://memory', 'r+');
+ fwrite($data, $sessionData);
+ rewind($data);
+ $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data";
+ break;
+ default:
+ $data = $sessionData;
+ $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
+ break;
+ }
+
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+ $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+
+ return $stmt;
+ }
+
+ /**
+ * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
+ *
+ * @param string $sessionId Session ID
+ * @param string $data Encoded session data
+ * @param int $maxlifetime session.gc_maxlifetime
+ *
+ * @return \PDOStatement|null The merge statement or null when not supported
+ */
+ private function getMergeStatement($sessionId, $data, $maxlifetime)
+ {
+ switch (true) {
+ case 'mysql' === $this->driver:
+ $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
+ "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
+ break;
+ case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
+ // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
+ // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
+ $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
+ "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+ "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
+ break;
+ case 'sqlite' === $this->driver:
+ $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
+ break;
+ case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='):
+ $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
+ "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
+ break;
+ default:
+ // MERGE is not supported with LOBs: http://www.oracle.com/technetwork/articles/fuecks-lobs-095315.html
+ return null;
+ }
+
+ $mergeStmt = $this->pdo->prepare($mergeSql);
+
+ if ('sqlsrv' === $this->driver) {
+ $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR);
+ $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR);
+ $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB);
+ $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT);
+ $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT);
+ $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB);
+ $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT);
+ $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT);
+ } else {
+ $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+ $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ }
+
+ return $mergeStmt;
+ }
+
+ /**
+ * Return a PDO instance.
+ *
+ * @return \PDO
+ */
+ protected function getConnection()
+ {
+ if (null === $this->pdo) {
+ $this->connect($this->dsn ?: ini_get('session.save_path'));
+ }
+
+ return $this->pdo;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
new file mode 100644
index 0000000..2281192
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
+ *
+ * @author Nicolas Grekas
+ */
+class StrictSessionHandler extends AbstractSessionHandler
+{
+ private $handler;
+ private $doDestroy;
+
+ public function __construct(\SessionHandlerInterface $handler)
+ {
+ if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
+ throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_class($handler), self::class));
+ }
+
+ $this->handler = $handler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ parent::open($savePath, $sessionName);
+
+ return $this->handler->open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ return $this->handler->read($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ return $this->write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ return $this->handler->write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ $this->doDestroy = true;
+ $destroyed = parent::destroy($sessionId);
+
+ return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ $this->doDestroy = false;
+
+ return $this->handler->destroy($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return $this->handler->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return $this->handler->gc($maxlifetime);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php
new file mode 100644
index 0000000..1541ec4
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', WriteCheckSessionHandler::class), E_USER_DEPRECATED);
+
+/**
+ * Wraps another SessionHandlerInterface to only write the session when it has been modified.
+ *
+ * @author Adrien Brault
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.
+ */
+class WriteCheckSessionHandler implements \SessionHandlerInterface
+{
+ private $wrappedSessionHandler;
+
+ /**
+ * @var array sessionId => session
+ */
+ private $readSessions;
+
+ public function __construct(\SessionHandlerInterface $wrappedSessionHandler)
+ {
+ $this->wrappedSessionHandler = $wrappedSessionHandler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return $this->wrappedSessionHandler->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ return $this->wrappedSessionHandler->destroy($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return $this->wrappedSessionHandler->gc($maxlifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ return $this->wrappedSessionHandler->open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ $session = $this->wrappedSessionHandler->read($sessionId);
+
+ $this->readSessions[$sessionId] = $session;
+
+ return $session;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) {
+ return true;
+ }
+
+ return $this->wrappedSessionHandler->write($sessionId, $data);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
new file mode 100644
index 0000000..6f59af4
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
@@ -0,0 +1,168 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * Metadata container.
+ *
+ * Adds metadata to the session.
+ *
+ * @author Drak
+ */
+class MetadataBag implements SessionBagInterface
+{
+ const CREATED = 'c';
+ const UPDATED = 'u';
+ const LIFETIME = 'l';
+
+ /**
+ * @var string
+ */
+ private $name = '__metadata';
+
+ /**
+ * @var string
+ */
+ private $storageKey;
+
+ /**
+ * @var array
+ */
+ protected $meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0);
+
+ /**
+ * Unix timestamp.
+ *
+ * @var int
+ */
+ private $lastUsed;
+
+ /**
+ * @var int
+ */
+ private $updateThreshold;
+
+ /**
+ * @param string $storageKey The key used to store bag in the session
+ * @param int $updateThreshold The time to wait between two UPDATED updates
+ */
+ public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0)
+ {
+ $this->storageKey = $storageKey;
+ $this->updateThreshold = $updateThreshold;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$array)
+ {
+ $this->meta = &$array;
+
+ if (isset($array[self::CREATED])) {
+ $this->lastUsed = $this->meta[self::UPDATED];
+
+ $timeStamp = time();
+ if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) {
+ $this->meta[self::UPDATED] = $timeStamp;
+ }
+ } else {
+ $this->stampCreated();
+ }
+ }
+
+ /**
+ * Gets the lifetime that the session cookie was set with.
+ *
+ * @return int
+ */
+ public function getLifetime()
+ {
+ return $this->meta[self::LIFETIME];
+ }
+
+ /**
+ * Stamps a new session's metadata.
+ *
+ * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+ * will leave the system settings unchanged, 0 sets the cookie
+ * to expire with browser session. Time is in seconds, and is
+ * not a Unix timestamp.
+ */
+ public function stampNew($lifetime = null)
+ {
+ $this->stampCreated($lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * Gets the created timestamp metadata.
+ *
+ * @return int Unix timestamp
+ */
+ public function getCreated()
+ {
+ return $this->meta[self::CREATED];
+ }
+
+ /**
+ * Gets the last used metadata.
+ *
+ * @return int Unix timestamp
+ */
+ public function getLastUsed()
+ {
+ return $this->lastUsed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // nothing to do
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets name.
+ *
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ private function stampCreated($lifetime = null)
+ {
+ $timeStamp = time();
+ $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
+ $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
new file mode 100644
index 0000000..027f4ef
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
@@ -0,0 +1,256 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * MockArraySessionStorage mocks the session for unit tests.
+ *
+ * No PHP session is actually started since a session can be initialized
+ * and shutdown only once per PHP execution cycle.
+ *
+ * When doing functional testing, you should use MockFileSessionStorage instead.
+ *
+ * @author Fabien Potencier
+ * @author Bulat Shakirzyanov
+ * @author Drak
+ */
+class MockArraySessionStorage implements SessionStorageInterface
+{
+ /**
+ * @var string
+ */
+ protected $id = '';
+
+ /**
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * @var bool
+ */
+ protected $started = false;
+
+ /**
+ * @var bool
+ */
+ protected $closed = false;
+
+ /**
+ * @var array
+ */
+ protected $data = array();
+
+ /**
+ * @var MetadataBag
+ */
+ protected $metadataBag;
+
+ /**
+ * @var array|SessionBagInterface[]
+ */
+ protected $bags = array();
+
+ /**
+ * @param string $name Session name
+ * @param MetadataBag $metaBag MetadataBag instance
+ */
+ public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null)
+ {
+ $this->name = $name;
+ $this->setMetadataBag($metaBag);
+ }
+
+ public function setSessionData(array $array)
+ {
+ $this->data = $array;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ if (empty($this->id)) {
+ $this->id = $this->generateId();
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function regenerate($destroy = false, $lifetime = null)
+ {
+ if (!$this->started) {
+ $this->start();
+ }
+
+ $this->metadataBag->stampNew($lifetime);
+ $this->id = $this->generateId();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setId($id)
+ {
+ if ($this->started) {
+ throw new \LogicException('Cannot set session ID after the session has started.');
+ }
+
+ $this->id = $id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save()
+ {
+ if (!$this->started || $this->closed) {
+ throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
+ }
+ // nothing to do since we don't persist the session data
+ $this->closed = false;
+ $this->started = false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // clear out the bags
+ foreach ($this->bags as $bag) {
+ $bag->clear();
+ }
+
+ // clear out the session
+ $this->data = array();
+
+ // reconnect the bags to the session
+ $this->loadSession();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerBag(SessionBagInterface $bag)
+ {
+ $this->bags[$bag->getName()] = $bag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBag($name)
+ {
+ if (!isset($this->bags[$name])) {
+ throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
+ }
+
+ if (!$this->started) {
+ $this->start();
+ }
+
+ return $this->bags[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isStarted()
+ {
+ return $this->started;
+ }
+
+ public function setMetadataBag(MetadataBag $bag = null)
+ {
+ if (null === $bag) {
+ $bag = new MetadataBag();
+ }
+
+ $this->metadataBag = $bag;
+ }
+
+ /**
+ * Gets the MetadataBag.
+ *
+ * @return MetadataBag
+ */
+ public function getMetadataBag()
+ {
+ return $this->metadataBag;
+ }
+
+ /**
+ * Generates a session ID.
+ *
+ * This doesn't need to be particularly cryptographically secure since this is just
+ * a mock.
+ *
+ * @return string
+ */
+ protected function generateId()
+ {
+ return hash('sha256', uniqid('ss_mock_', true));
+ }
+
+ protected function loadSession()
+ {
+ $bags = array_merge($this->bags, array($this->metadataBag));
+
+ foreach ($bags as $bag) {
+ $key = $bag->getStorageKey();
+ $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array();
+ $bag->initialize($this->data[$key]);
+ }
+
+ $this->started = true;
+ $this->closed = false;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
new file mode 100644
index 0000000..14f4270
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
@@ -0,0 +1,152 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+/**
+ * MockFileSessionStorage is used to mock sessions for
+ * functional testing when done in a single PHP process.
+ *
+ * No PHP session is actually started since a session can be initialized
+ * and shutdown only once per PHP execution cycle and this class does
+ * not pollute any session related globals, including session_*() functions
+ * or session.* PHP ini directives.
+ *
+ * @author Drak
+ */
+class MockFileSessionStorage extends MockArraySessionStorage
+{
+ private $savePath;
+
+ /**
+ * @param string $savePath Path of directory to save session files
+ * @param string $name Session name
+ * @param MetadataBag $metaBag MetadataBag instance
+ */
+ public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null)
+ {
+ if (null === $savePath) {
+ $savePath = sys_get_temp_dir();
+ }
+
+ if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
+ throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath));
+ }
+
+ $this->savePath = $savePath;
+
+ parent::__construct($name, $metaBag);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ if (!$this->id) {
+ $this->id = $this->generateId();
+ }
+
+ $this->read();
+
+ $this->started = true;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function regenerate($destroy = false, $lifetime = null)
+ {
+ if (!$this->started) {
+ $this->start();
+ }
+
+ if ($destroy) {
+ $this->destroy();
+ }
+
+ return parent::regenerate($destroy, $lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save()
+ {
+ if (!$this->started) {
+ throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
+ }
+
+ $data = $this->data;
+
+ foreach ($this->bags as $bag) {
+ if (empty($data[$key = $bag->getStorageKey()])) {
+ unset($data[$key]);
+ }
+ }
+ if (array($key = $this->metadataBag->getStorageKey()) === array_keys($data)) {
+ unset($data[$key]);
+ }
+
+ try {
+ if ($data) {
+ file_put_contents($this->getFilePath(), serialize($data));
+ } else {
+ $this->destroy();
+ }
+ } finally {
+ $this->data = $data;
+ }
+
+ // this is needed for Silex, where the session object is re-used across requests
+ // in functional tests. In Symfony, the container is rebooted, so we don't have
+ // this issue
+ $this->started = false;
+ }
+
+ /**
+ * Deletes a session from persistent storage.
+ * Deliberately leaves session data in memory intact.
+ */
+ private function destroy()
+ {
+ if (is_file($this->getFilePath())) {
+ unlink($this->getFilePath());
+ }
+ }
+
+ /**
+ * Calculate path to file.
+ *
+ * @return string File path
+ */
+ private function getFilePath()
+ {
+ return $this->savePath.'/'.$this->id.'.mocksess';
+ }
+
+ /**
+ * Reads session from storage and loads session.
+ */
+ private function read()
+ {
+ $filePath = $this->getFilePath();
+ $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array();
+
+ $this->loadSession();
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
new file mode 100644
index 0000000..0dfad9a
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
@@ -0,0 +1,445 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * This provides a base class for session attribute storage.
+ *
+ * @author Drak
+ */
+class NativeSessionStorage implements SessionStorageInterface
+{
+ /**
+ * @var SessionBagInterface[]
+ */
+ protected $bags = array();
+
+ /**
+ * @var bool
+ */
+ protected $started = false;
+
+ /**
+ * @var bool
+ */
+ protected $closed = false;
+
+ /**
+ * @var AbstractProxy|\SessionHandlerInterface
+ */
+ protected $saveHandler;
+
+ /**
+ * @var MetadataBag
+ */
+ protected $metadataBag;
+
+ /**
+ * Depending on how you want the storage driver to behave you probably
+ * want to override this constructor entirely.
+ *
+ * List of options for $options array with their defaults.
+ *
+ * @see http://php.net/session.configuration for options
+ * but we omit 'session.' from the beginning of the keys for convenience.
+ *
+ * ("auto_start", is not supported as it tells PHP to start a session before
+ * PHP starts to execute user-land code. Setting during runtime has no effect).
+ *
+ * cache_limiter, "" (use "0" to prevent headers from being sent entirely).
+ * cache_expire, "0"
+ * cookie_domain, ""
+ * cookie_httponly, ""
+ * cookie_lifetime, "0"
+ * cookie_path, "/"
+ * cookie_secure, ""
+ * entropy_file, ""
+ * entropy_length, "0"
+ * gc_divisor, "100"
+ * gc_maxlifetime, "1440"
+ * gc_probability, "1"
+ * hash_bits_per_character, "4"
+ * hash_function, "0"
+ * lazy_write, "1"
+ * name, "PHPSESSID"
+ * referer_check, ""
+ * serialize_handler, "php"
+ * use_strict_mode, "0"
+ * use_cookies, "1"
+ * use_only_cookies, "1"
+ * use_trans_sid, "0"
+ * upload_progress.enabled, "1"
+ * upload_progress.cleanup, "1"
+ * upload_progress.prefix, "upload_progress_"
+ * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
+ * upload_progress.freq, "1%"
+ * upload_progress.min-freq, "1"
+ * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
+ * sid_length, "32"
+ * sid_bits_per_character, "5"
+ * trans_sid_hosts, $_SERVER['HTTP_HOST']
+ * trans_sid_tags, "a=href,area=href,frame=src,form="
+ *
+ * @param array $options Session configuration options
+ * @param \SessionHandlerInterface|null $handler
+ * @param MetadataBag $metaBag MetadataBag
+ */
+ public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
+ {
+ $options += array(
+ 'cache_limiter' => '',
+ 'cache_expire' => 0,
+ 'use_cookies' => 1,
+ 'lazy_write' => 1,
+ );
+
+ session_register_shutdown();
+
+ $this->setMetadataBag($metaBag);
+ $this->setOptions($options);
+ $this->setSaveHandler($handler);
+ }
+
+ /**
+ * Gets the save handler instance.
+ *
+ * @return AbstractProxy|\SessionHandlerInterface
+ */
+ public function getSaveHandler()
+ {
+ return $this->saveHandler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ if (\PHP_SESSION_ACTIVE === session_status()) {
+ throw new \RuntimeException('Failed to start the session: already started by PHP.');
+ }
+
+ if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
+ throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
+ }
+
+ // ok to try and start the session
+ if (!session_start()) {
+ throw new \RuntimeException('Failed to start the session');
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getId()
+ {
+ return $this->saveHandler->getId();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setId($id)
+ {
+ $this->saveHandler->setId($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->saveHandler->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name)
+ {
+ $this->saveHandler->setName($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function regenerate($destroy = false, $lifetime = null)
+ {
+ // Cannot regenerate the session ID for non-active sessions.
+ if (\PHP_SESSION_ACTIVE !== session_status()) {
+ return false;
+ }
+
+ if (headers_sent()) {
+ return false;
+ }
+
+ if (null !== $lifetime) {
+ ini_set('session.cookie_lifetime', $lifetime);
+ }
+
+ if ($destroy) {
+ $this->metadataBag->stampNew();
+ }
+
+ $isRegenerated = session_regenerate_id($destroy);
+
+ // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
+ // @see https://bugs.php.net/bug.php?id=70013
+ $this->loadSession();
+
+ return $isRegenerated;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save()
+ {
+ $session = $_SESSION;
+
+ foreach ($this->bags as $bag) {
+ if (empty($_SESSION[$key = $bag->getStorageKey()])) {
+ unset($_SESSION[$key]);
+ }
+ }
+ if (array($key = $this->metadataBag->getStorageKey()) === array_keys($_SESSION)) {
+ unset($_SESSION[$key]);
+ }
+
+ // Register custom error handler to catch a possible failure warning during session write
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) {
+ throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline);
+ }, E_WARNING);
+
+ try {
+ $e = null;
+ session_write_close();
+ } catch (\ErrorException $e) {
+ } finally {
+ restore_error_handler();
+ $_SESSION = $session;
+ }
+ if (null !== $e) {
+ // The default PHP error message is not very helpful, as it does not give any information on the current save handler.
+ // Therefore, we catch this error and trigger a warning with a better error message
+ $handler = $this->getSaveHandler();
+ if ($handler instanceof SessionHandlerProxy) {
+ $handler = $handler->getHandler();
+ }
+
+ trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING);
+ }
+
+ $this->closed = true;
+ $this->started = false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // clear out the bags
+ foreach ($this->bags as $bag) {
+ $bag->clear();
+ }
+
+ // clear out the session
+ $_SESSION = array();
+
+ // reconnect the bags to the session
+ $this->loadSession();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerBag(SessionBagInterface $bag)
+ {
+ if ($this->started) {
+ throw new \LogicException('Cannot register a bag when the session is already started.');
+ }
+
+ $this->bags[$bag->getName()] = $bag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBag($name)
+ {
+ if (!isset($this->bags[$name])) {
+ throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
+ }
+
+ if (!$this->started && $this->saveHandler->isActive()) {
+ $this->loadSession();
+ } elseif (!$this->started) {
+ $this->start();
+ }
+
+ return $this->bags[$name];
+ }
+
+ public function setMetadataBag(MetadataBag $metaBag = null)
+ {
+ if (null === $metaBag) {
+ $metaBag = new MetadataBag();
+ }
+
+ $this->metadataBag = $metaBag;
+ }
+
+ /**
+ * Gets the MetadataBag.
+ *
+ * @return MetadataBag
+ */
+ public function getMetadataBag()
+ {
+ return $this->metadataBag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isStarted()
+ {
+ return $this->started;
+ }
+
+ /**
+ * Sets session.* ini variables.
+ *
+ * For convenience we omit 'session.' from the beginning of the keys.
+ * Explicitly ignores other ini keys.
+ *
+ * @param array $options Session ini directives array(key => value)
+ *
+ * @see http://php.net/session.configuration
+ */
+ public function setOptions(array $options)
+ {
+ if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+ return;
+ }
+
+ $validOptions = array_flip(array(
+ 'cache_limiter', 'cache_expire', 'cookie_domain', 'cookie_httponly',
+ 'cookie_lifetime', 'cookie_path', 'cookie_secure',
+ 'entropy_file', 'entropy_length', 'gc_divisor',
+ 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
+ 'hash_function', 'lazy_write', 'name', 'referer_check',
+ 'serialize_handler', 'use_strict_mode', 'use_cookies',
+ 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
+ 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
+ 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags',
+ 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
+ ));
+
+ foreach ($options as $key => $value) {
+ if (isset($validOptions[$key])) {
+ ini_set('session.'.$key, $value);
+ }
+ }
+ }
+
+ /**
+ * Registers session save handler as a PHP session handler.
+ *
+ * To use internal PHP session save handlers, override this method using ini_set with
+ * session.save_handler and session.save_path e.g.
+ *
+ * ini_set('session.save_handler', 'files');
+ * ini_set('session.save_path', '/tmp');
+ *
+ * or pass in a \SessionHandler instance which configures session.save_handler in the
+ * constructor, for a template see NativeFileSessionHandler or use handlers in
+ * composer package drak/native-session
+ *
+ * @see http://php.net/session-set-save-handler
+ * @see http://php.net/sessionhandlerinterface
+ * @see http://php.net/sessionhandler
+ * @see http://github.com/drak/NativeSession
+ *
+ * @param \SessionHandlerInterface|null $saveHandler
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setSaveHandler($saveHandler = null)
+ {
+ if (!$saveHandler instanceof AbstractProxy &&
+ !$saveHandler instanceof \SessionHandlerInterface &&
+ null !== $saveHandler) {
+ throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
+ }
+
+ // Wrap $saveHandler in proxy and prevent double wrapping of proxy
+ if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
+ $saveHandler = new SessionHandlerProxy($saveHandler);
+ } elseif (!$saveHandler instanceof AbstractProxy) {
+ $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
+ }
+ $this->saveHandler = $saveHandler;
+
+ if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+ return;
+ }
+
+ if ($this->saveHandler instanceof SessionHandlerProxy) {
+ session_set_save_handler($this->saveHandler->getHandler(), false);
+ } elseif ($this->saveHandler instanceof \SessionHandlerInterface) {
+ session_set_save_handler($this->saveHandler, false);
+ }
+ }
+
+ /**
+ * Load the session with attributes.
+ *
+ * After starting the session, PHP retrieves the session from whatever handlers
+ * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
+ * PHP takes the return value from the read() handler, unserializes it
+ * and populates $_SESSION with the result automatically.
+ */
+ protected function loadSession(array &$session = null)
+ {
+ if (null === $session) {
+ $session = &$_SESSION;
+ }
+
+ $bags = array_merge($this->bags, array($this->metadataBag));
+
+ foreach ($bags as $bag) {
+ $key = $bag->getStorageKey();
+ $session[$key] = isset($session[$key]) ? $session[$key] : array();
+ $bag->initialize($session[$key]);
+ }
+
+ $this->started = true;
+ $this->closed = false;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
new file mode 100644
index 0000000..662ed50
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+/**
+ * Allows session to be started by PHP and managed by Symfony.
+ *
+ * @author Drak
+ */
+class PhpBridgeSessionStorage extends NativeSessionStorage
+{
+ /**
+ * @param \SessionHandlerInterface|null $handler
+ * @param MetadataBag $metaBag MetadataBag
+ */
+ public function __construct($handler = null, MetadataBag $metaBag = null)
+ {
+ $this->setMetadataBag($metaBag);
+ $this->setSaveHandler($handler);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // clear out the bags and nothing else that may be set
+ // since the purpose of this driver is to share a handler
+ foreach ($this->bags as $bag) {
+ $bag->clear();
+ }
+
+ // reconnect the bags to the session
+ $this->loadSession();
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
new file mode 100644
index 0000000..09c9248
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+/**
+ * @author Drak
+ */
+abstract class AbstractProxy
+{
+ /**
+ * Flag if handler wraps an internal PHP session handler (using \SessionHandler).
+ *
+ * @var bool
+ */
+ protected $wrapper = false;
+
+ /**
+ * @var string
+ */
+ protected $saveHandlerName;
+
+ /**
+ * Gets the session.save_handler name.
+ *
+ * @return string
+ */
+ public function getSaveHandlerName()
+ {
+ return $this->saveHandlerName;
+ }
+
+ /**
+ * Is this proxy handler and instance of \SessionHandlerInterface.
+ *
+ * @return bool
+ */
+ public function isSessionHandlerInterface()
+ {
+ return $this instanceof \SessionHandlerInterface;
+ }
+
+ /**
+ * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
+ *
+ * @return bool
+ */
+ public function isWrapper()
+ {
+ return $this->wrapper;
+ }
+
+ /**
+ * Has a session started?
+ *
+ * @return bool
+ */
+ public function isActive()
+ {
+ return \PHP_SESSION_ACTIVE === session_status();
+ }
+
+ /**
+ * Gets the session ID.
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ return session_id();
+ }
+
+ /**
+ * Sets the session ID.
+ *
+ * @param string $id
+ *
+ * @throws \LogicException
+ */
+ public function setId($id)
+ {
+ if ($this->isActive()) {
+ throw new \LogicException('Cannot change the ID of an active session');
+ }
+
+ session_id($id);
+ }
+
+ /**
+ * Gets the session name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return session_name();
+ }
+
+ /**
+ * Sets the session name.
+ *
+ * @param string $name
+ *
+ * @throws \LogicException
+ */
+ public function setName($name)
+ {
+ if ($this->isActive()) {
+ throw new \LogicException('Cannot change the name of an active session');
+ }
+
+ session_name($name);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php
new file mode 100644
index 0000000..082eed1
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+@trigger_error('The '.__NAMESPACE__.'\NativeProxy class is deprecated since Symfony 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED);
+
+/**
+ * This proxy is built-in session handlers in PHP 5.3.x.
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly.
+ *
+ * @author Drak
+ */
+class NativeProxy extends AbstractProxy
+{
+ public function __construct()
+ {
+ // this makes an educated guess as to what the handler is since it should already be set.
+ $this->saveHandlerName = ini_get('session.save_handler');
+ }
+
+ /**
+ * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
+ *
+ * @return bool False
+ */
+ public function isWrapper()
+ {
+ return false;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
new file mode 100644
index 0000000..53c1209
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+/**
+ * @author Drak
+ */
+class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
+{
+ protected $handler;
+
+ public function __construct(\SessionHandlerInterface $handler)
+ {
+ $this->handler = $handler;
+ $this->wrapper = ($handler instanceof \SessionHandler);
+ $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
+ }
+
+ /**
+ * @return \SessionHandlerInterface
+ */
+ public function getHandler()
+ {
+ return $this->handler;
+ }
+
+ // \SessionHandlerInterface
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ return (bool) $this->handler->open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return (bool) $this->handler->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ return (string) $this->handler->read($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ return (bool) $this->handler->write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ return (bool) $this->handler->destroy($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return (bool) $this->handler->gc($maxlifetime);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/assets/php/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
new file mode 100644
index 0000000..66e8b33
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
@@ -0,0 +1,137 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * StorageInterface.
+ *
+ * @author Fabien Potencier
+ * @author Drak
+ */
+interface SessionStorageInterface
+{
+ /**
+ * Starts the session.
+ *
+ * @return bool True if started
+ *
+ * @throws \RuntimeException if something goes wrong starting the session
+ */
+ public function start();
+
+ /**
+ * Checks if the session is started.
+ *
+ * @return bool True if started, false otherwise
+ */
+ public function isStarted();
+
+ /**
+ * Returns the session ID.
+ *
+ * @return string The session ID or empty
+ */
+ public function getId();
+
+ /**
+ * Sets the session ID.
+ *
+ * @param string $id
+ */
+ public function setId($id);
+
+ /**
+ * Returns the session name.
+ *
+ * @return mixed The session name
+ */
+ public function getName();
+
+ /**
+ * Sets the session name.
+ *
+ * @param string $name
+ */
+ public function setName($name);
+
+ /**
+ * Regenerates id that represents this storage.
+ *
+ * This method must invoke session_regenerate_id($destroy) unless
+ * this interface is used for a storage object designed for unit
+ * or functional testing where a real PHP session would interfere
+ * with testing.
+ *
+ * Note regenerate+destroy should not clear the session data in memory
+ * only delete the session data from persistent storage.
+ *
+ * Care: When regenerating the session ID no locking is involved in PHP's
+ * session design. See https://bugs.php.net/bug.php?id=61470 for a discussion.
+ * So you must make sure the regenerated session is saved BEFORE sending the
+ * headers with the new ID. Symfony's HttpKernel offers a listener for this.
+ * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener.
+ * Otherwise session data could get lost again for concurrent requests with the
+ * new ID. One result could be that you get logged out after just logging in.
+ *
+ * @param bool $destroy Destroy session when regenerating?
+ * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+ * will leave the system settings unchanged, 0 sets the cookie
+ * to expire with browser session. Time is in seconds, and is
+ * not a Unix timestamp.
+ *
+ * @return bool True if session regenerated, false if error
+ *
+ * @throws \RuntimeException If an error occurs while regenerating this storage
+ */
+ public function regenerate($destroy = false, $lifetime = null);
+
+ /**
+ * Force the session to be saved and closed.
+ *
+ * This method must invoke session_write_close() unless this interface is
+ * used for a storage object design for unit or functional testing where
+ * a real PHP session would interfere with testing, in which case
+ * it should actually persist the session data if required.
+ *
+ * @throws \RuntimeException if the session is saved without being started, or if the session
+ * is already closed
+ */
+ public function save();
+
+ /**
+ * Clear all session data in memory.
+ */
+ public function clear();
+
+ /**
+ * Gets a SessionBagInterface by name.
+ *
+ * @param string $name
+ *
+ * @return SessionBagInterface
+ *
+ * @throws \InvalidArgumentException If the bag does not exist
+ */
+ public function getBag($name);
+
+ /**
+ * Registers a SessionBagInterface for use.
+ */
+ public function registerBag(SessionBagInterface $bag);
+
+ /**
+ * @return MetadataBag
+ */
+ public function getMetadataBag();
+}
diff --git a/assets/php/vendor/symfony/http-foundation/StreamedResponse.php b/assets/php/vendor/symfony/http-foundation/StreamedResponse.php
new file mode 100644
index 0000000..92868d3
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/StreamedResponse.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * StreamedResponse represents a streamed HTTP response.
+ *
+ * A StreamedResponse uses a callback for its content.
+ *
+ * The callback should use the standard PHP functions like echo
+ * to stream the response back to the client. The flush() method
+ * can also be used if needed.
+ *
+ * @see flush()
+ *
+ * @author Fabien Potencier
+ */
+class StreamedResponse extends Response
+{
+ protected $callback;
+ protected $streamed;
+ private $headersSent;
+
+ /**
+ * @param callable|null $callback A valid PHP callback or null to set it later
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ */
+ public function __construct(callable $callback = null, $status = 200, $headers = array())
+ {
+ parent::__construct(null, $status, $headers);
+
+ if (null !== $callback) {
+ $this->setCallback($callback);
+ }
+ $this->streamed = false;
+ $this->headersSent = false;
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * @param callable|null $callback A valid PHP callback or null to set it later
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($callback = null, $status = 200, $headers = array())
+ {
+ return new static($callback, $status, $headers);
+ }
+
+ /**
+ * Sets the PHP callback associated with this Response.
+ *
+ * @param callable $callback A valid PHP callback
+ *
+ * @return $this
+ */
+ public function setCallback(callable $callback)
+ {
+ $this->callback = $callback;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * This method only sends the headers once.
+ *
+ * @return $this
+ */
+ public function sendHeaders()
+ {
+ if ($this->headersSent) {
+ return $this;
+ }
+
+ $this->headersSent = true;
+
+ return parent::sendHeaders();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * This method only sends the content once.
+ *
+ * @return $this
+ */
+ public function sendContent()
+ {
+ if ($this->streamed) {
+ return $this;
+ }
+
+ $this->streamed = true;
+
+ if (null === $this->callback) {
+ throw new \LogicException('The Response callback must not be null.');
+ }
+
+ call_user_func($this->callback);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \LogicException when the content is not null
+ *
+ * @return $this
+ */
+ public function setContent($content)
+ {
+ if (null !== $content) {
+ throw new \LogicException('The content cannot be set on a StreamedResponse instance.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return false
+ */
+ public function getContent()
+ {
+ return false;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php b/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php
new file mode 100644
index 0000000..cb43bb3
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\AcceptHeaderItem;
+
+class AcceptHeaderItemTest extends TestCase
+{
+ /**
+ * @dataProvider provideFromStringData
+ */
+ public function testFromString($string, $value, array $attributes)
+ {
+ $item = AcceptHeaderItem::fromString($string);
+ $this->assertEquals($value, $item->getValue());
+ $this->assertEquals($attributes, $item->getAttributes());
+ }
+
+ public function provideFromStringData()
+ {
+ return array(
+ array(
+ 'text/html',
+ 'text/html', array(),
+ ),
+ array(
+ '"this;should,not=matter"',
+ 'this;should,not=matter', array(),
+ ),
+ array(
+ "text/plain; charset=utf-8;param=\"this;should,not=matter\";\tfootnotes=true",
+ 'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'),
+ ),
+ array(
+ '"this;should,not=matter";charset=utf-8',
+ 'this;should,not=matter', array('charset' => 'utf-8'),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideToStringData
+ */
+ public function testToString($value, array $attributes, $string)
+ {
+ $item = new AcceptHeaderItem($value, $attributes);
+ $this->assertEquals($string, (string) $item);
+ }
+
+ public function provideToStringData()
+ {
+ return array(
+ array(
+ 'text/html', array(),
+ 'text/html',
+ ),
+ array(
+ 'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'),
+ 'text/plain;charset=utf-8;param="this;should,not=matter";footnotes=true',
+ ),
+ );
+ }
+
+ public function testValue()
+ {
+ $item = new AcceptHeaderItem('value', array());
+ $this->assertEquals('value', $item->getValue());
+
+ $item->setValue('new value');
+ $this->assertEquals('new value', $item->getValue());
+
+ $item->setValue(1);
+ $this->assertEquals('1', $item->getValue());
+ }
+
+ public function testQuality()
+ {
+ $item = new AcceptHeaderItem('value', array());
+ $this->assertEquals(1.0, $item->getQuality());
+
+ $item->setQuality(0.5);
+ $this->assertEquals(0.5, $item->getQuality());
+
+ $item->setAttribute('q', 0.75);
+ $this->assertEquals(0.75, $item->getQuality());
+ $this->assertFalse($item->hasAttribute('q'));
+ }
+
+ public function testAttribute()
+ {
+ $item = new AcceptHeaderItem('value', array());
+ $this->assertEquals(array(), $item->getAttributes());
+ $this->assertFalse($item->hasAttribute('test'));
+ $this->assertNull($item->getAttribute('test'));
+ $this->assertEquals('default', $item->getAttribute('test', 'default'));
+
+ $item->setAttribute('test', 'value');
+ $this->assertEquals(array('test' => 'value'), $item->getAttributes());
+ $this->assertTrue($item->hasAttribute('test'));
+ $this->assertEquals('value', $item->getAttribute('test'));
+ $this->assertEquals('value', $item->getAttribute('test', 'default'));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php b/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php
new file mode 100644
index 0000000..9929eac
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\AcceptHeader;
+use Symfony\Component\HttpFoundation\AcceptHeaderItem;
+
+class AcceptHeaderTest extends TestCase
+{
+ public function testFirst()
+ {
+ $header = AcceptHeader::fromString('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c');
+ $this->assertSame('text/html', $header->first()->getValue());
+ }
+
+ /**
+ * @dataProvider provideFromStringData
+ */
+ public function testFromString($string, array $items)
+ {
+ $header = AcceptHeader::fromString($string);
+ $parsed = array_values($header->all());
+ // reset index since the fixtures don't have them set
+ foreach ($parsed as $item) {
+ $item->setIndex(0);
+ }
+ $this->assertEquals($items, $parsed);
+ }
+
+ public function provideFromStringData()
+ {
+ return array(
+ array('', array()),
+ array('gzip', array(new AcceptHeaderItem('gzip'))),
+ array('gzip,deflate,sdch', array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))),
+ array("gzip, deflate\t,sdch", array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))),
+ array('"this;should,not=matter"', array(new AcceptHeaderItem('this;should,not=matter'))),
+ );
+ }
+
+ /**
+ * @dataProvider provideToStringData
+ */
+ public function testToString(array $items, $string)
+ {
+ $header = new AcceptHeader($items);
+ $this->assertEquals($string, (string) $header);
+ }
+
+ public function provideToStringData()
+ {
+ return array(
+ array(array(), ''),
+ array(array(new AcceptHeaderItem('gzip')), 'gzip'),
+ array(array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch')), 'gzip,deflate,sdch'),
+ array(array(new AcceptHeaderItem('this;should,not=matter')), 'this;should,not=matter'),
+ );
+ }
+
+ /**
+ * @dataProvider provideFilterData
+ */
+ public function testFilter($string, $filter, array $values)
+ {
+ $header = AcceptHeader::fromString($string)->filter($filter);
+ $this->assertEquals($values, array_keys($header->all()));
+ }
+
+ public function provideFilterData()
+ {
+ return array(
+ array('fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4', '/fr.*/', array('fr-FR', 'fr')),
+ );
+ }
+
+ /**
+ * @dataProvider provideSortingData
+ */
+ public function testSorting($string, array $values)
+ {
+ $header = AcceptHeader::fromString($string);
+ $this->assertEquals($values, array_keys($header->all()));
+ }
+
+ public function provideSortingData()
+ {
+ return array(
+ 'quality has priority' => array('*;q=0.3,ISO-8859-1,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')),
+ 'order matters when q is equal' => array('*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')),
+ 'order matters when q is equal2' => array('*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7', array('utf-8', 'ISO-8859-1', '*')),
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php
new file mode 100644
index 0000000..157ab90
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ApacheRequest;
+
+class ApacheRequestTest extends TestCase
+{
+ /**
+ * @dataProvider provideServerVars
+ */
+ public function testUriMethods($server, $expectedRequestUri, $expectedBaseUrl, $expectedPathInfo)
+ {
+ $request = new ApacheRequest();
+ $request->server->replace($server);
+
+ $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct');
+ $this->assertEquals($expectedBaseUrl, $request->getBaseUrl(), '->getBaseUrl() is correct');
+ $this->assertEquals($expectedPathInfo, $request->getPathInfo(), '->getPathInfo() is correct');
+ }
+
+ public function provideServerVars()
+ {
+ return array(
+ array(
+ array(
+ 'REQUEST_URI' => '/foo/app_dev.php/bar',
+ 'SCRIPT_NAME' => '/foo/app_dev.php',
+ 'PATH_INFO' => '/bar',
+ ),
+ '/foo/app_dev.php/bar',
+ '/foo/app_dev.php',
+ '/bar',
+ ),
+ array(
+ array(
+ 'REQUEST_URI' => '/foo/bar',
+ 'SCRIPT_NAME' => '/foo/app_dev.php',
+ ),
+ '/foo/bar',
+ '/foo',
+ '/bar',
+ ),
+ array(
+ array(
+ 'REQUEST_URI' => '/app_dev.php/foo/bar',
+ 'SCRIPT_NAME' => '/app_dev.php',
+ 'PATH_INFO' => '/foo/bar',
+ ),
+ '/app_dev.php/foo/bar',
+ '/app_dev.php',
+ '/foo/bar',
+ ),
+ array(
+ array(
+ 'REQUEST_URI' => '/foo/bar',
+ 'SCRIPT_NAME' => '/app_dev.php',
+ ),
+ '/foo/bar',
+ '',
+ '/foo/bar',
+ ),
+ array(
+ array(
+ 'REQUEST_URI' => '/app_dev.php',
+ 'SCRIPT_NAME' => '/app_dev.php',
+ ),
+ '/app_dev.php',
+ '/app_dev.php',
+ '/',
+ ),
+ array(
+ array(
+ 'REQUEST_URI' => '/',
+ 'SCRIPT_NAME' => '/app_dev.php',
+ ),
+ '/',
+ '',
+ '/',
+ ),
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php
new file mode 100644
index 0000000..1b9e589
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php
@@ -0,0 +1,352 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\File\Stream;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+use Symfony\Component\HttpFoundation\Tests\File\FakeFile;
+
+class BinaryFileResponseTest extends ResponseTestCase
+{
+ public function testConstruction()
+ {
+ $file = __DIR__.'/../README.md';
+ $response = new BinaryFileResponse($file, 404, array('X-Header' => 'Foo'), true, null, true, true);
+ $this->assertEquals(404, $response->getStatusCode());
+ $this->assertEquals('Foo', $response->headers->get('X-Header'));
+ $this->assertTrue($response->headers->has('ETag'));
+ $this->assertTrue($response->headers->has('Last-Modified'));
+ $this->assertFalse($response->headers->has('Content-Disposition'));
+
+ $response = BinaryFileResponse::create($file, 404, array(), true, ResponseHeaderBag::DISPOSITION_INLINE);
+ $this->assertEquals(404, $response->getStatusCode());
+ $this->assertFalse($response->headers->has('ETag'));
+ $this->assertEquals('inline; filename="README.md"', $response->headers->get('Content-Disposition'));
+ }
+
+ public function testConstructWithNonAsciiFilename()
+ {
+ touch(sys_get_temp_dir().'/fööö.html');
+
+ $response = new BinaryFileResponse(sys_get_temp_dir().'/fööö.html', 200, array(), true, 'attachment');
+
+ @unlink(sys_get_temp_dir().'/fööö.html');
+
+ $this->assertSame('fööö.html', $response->getFile()->getFilename());
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testSetContent()
+ {
+ $response = new BinaryFileResponse(__FILE__);
+ $response->setContent('foo');
+ }
+
+ public function testGetContent()
+ {
+ $response = new BinaryFileResponse(__FILE__);
+ $this->assertFalse($response->getContent());
+ }
+
+ public function testSetContentDispositionGeneratesSafeFallbackFilename()
+ {
+ $response = new BinaryFileResponse(__FILE__);
+ $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'föö.html');
+
+ $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition'));
+ }
+
+ public function testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename()
+ {
+ $response = new BinaryFileResponse(__FILE__);
+
+ $iso88591EncodedFilename = utf8_decode('föö.html');
+ $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $iso88591EncodedFilename);
+
+ // the parameter filename* is invalid in this case (rawurldecode('f%F6%F6') does not provide a UTF-8 string but an ISO-8859-1 encoded one)
+ $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition'));
+ }
+
+ /**
+ * @dataProvider provideRanges
+ */
+ public function testRequests($requestRange, $offset, $length, $responseRange)
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();
+
+ // do a request to get the ETag
+ $request = Request::create('/');
+ $response->prepare($request);
+ $etag = $response->headers->get('ETag');
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('If-Range', $etag);
+ $request->headers->set('Range', $requestRange);
+
+ $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+ fseek($file, $offset);
+ $data = fread($file, $length);
+ fclose($file);
+
+ $this->expectOutputString($data);
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(206, $response->getStatusCode());
+ $this->assertEquals($responseRange, $response->headers->get('Content-Range'));
+ $this->assertSame($length, $response->headers->get('Content-Length'));
+ }
+
+ /**
+ * @dataProvider provideRanges
+ */
+ public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange)
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));
+
+ // do a request to get the LastModified
+ $request = Request::create('/');
+ $response->prepare($request);
+ $lastModified = $response->headers->get('Last-Modified');
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('If-Range', $lastModified);
+ $request->headers->set('Range', $requestRange);
+
+ $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+ fseek($file, $offset);
+ $data = fread($file, $length);
+ fclose($file);
+
+ $this->expectOutputString($data);
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(206, $response->getStatusCode());
+ $this->assertEquals($responseRange, $response->headers->get('Content-Range'));
+ }
+
+ public function provideRanges()
+ {
+ return array(
+ array('bytes=1-4', 1, 4, 'bytes 1-4/35'),
+ array('bytes=-5', 30, 5, 'bytes 30-34/35'),
+ array('bytes=30-', 30, 5, 'bytes 30-34/35'),
+ array('bytes=30-30', 30, 1, 'bytes 30-30/35'),
+ array('bytes=30-34', 30, 5, 'bytes 30-34/35'),
+ );
+ }
+
+ public function testRangeRequestsWithoutLastModifiedDate()
+ {
+ // prevent auto last modified
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'), true, null, false, false);
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('If-Range', date('D, d M Y H:i:s').' GMT');
+ $request->headers->set('Range', 'bytes=1-4');
+
+ $this->expectOutputString(file_get_contents(__DIR__.'/File/Fixtures/test.gif'));
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertNull($response->headers->get('Content-Range'));
+ }
+
+ /**
+ * @dataProvider provideFullFileRanges
+ */
+ public function testFullFileRequests($requestRange)
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('Range', $requestRange);
+
+ $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+ $data = fread($file, 35);
+ fclose($file);
+
+ $this->expectOutputString($data);
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(200, $response->getStatusCode());
+ }
+
+ public function provideFullFileRanges()
+ {
+ return array(
+ array('bytes=0-'),
+ array('bytes=0-34'),
+ array('bytes=-35'),
+ // Syntactical invalid range-request should also return the full resource
+ array('bytes=20-10'),
+ array('bytes=50-40'),
+ );
+ }
+
+ /**
+ * @dataProvider provideInvalidRanges
+ */
+ public function testInvalidRequests($requestRange)
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag();
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('Range', $requestRange);
+
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(416, $response->getStatusCode());
+ $this->assertEquals('bytes */35', $response->headers->get('Content-Range'));
+ }
+
+ public function provideInvalidRanges()
+ {
+ return array(
+ array('bytes=-40'),
+ array('bytes=30-40'),
+ );
+ }
+
+ /**
+ * @dataProvider provideXSendfileFiles
+ */
+ public function testXSendfile($file)
+ {
+ $request = Request::create('/');
+ $request->headers->set('X-Sendfile-Type', 'X-Sendfile');
+
+ BinaryFileResponse::trustXSendfileTypeHeader();
+ $response = BinaryFileResponse::create($file, 200, array('Content-Type' => 'application/octet-stream'));
+ $response->prepare($request);
+
+ $this->expectOutputString('');
+ $response->sendContent();
+
+ $this->assertContains('README.md', $response->headers->get('X-Sendfile'));
+ }
+
+ public function provideXSendfileFiles()
+ {
+ return array(
+ array(__DIR__.'/../README.md'),
+ array('file://'.__DIR__.'/../README.md'),
+ );
+ }
+
+ /**
+ * @dataProvider getSampleXAccelMappings
+ */
+ public function testXAccelMapping($realpath, $mapping, $virtual)
+ {
+ $request = Request::create('/');
+ $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect');
+ $request->headers->set('X-Accel-Mapping', $mapping);
+
+ $file = new FakeFile($realpath, __DIR__.'/File/Fixtures/test');
+
+ BinaryFileResponse::trustXSendfileTypeHeader();
+ $response = new BinaryFileResponse($file, 200, array('Content-Type' => 'application/octet-stream'));
+ $reflection = new \ReflectionObject($response);
+ $property = $reflection->getProperty('file');
+ $property->setAccessible(true);
+ $property->setValue($response, $file);
+
+ $response->prepare($request);
+ $this->assertEquals($virtual, $response->headers->get('X-Accel-Redirect'));
+ }
+
+ public function testDeleteFileAfterSend()
+ {
+ $request = Request::create('/');
+
+ $path = __DIR__.'/File/Fixtures/to_delete';
+ touch($path);
+ $realPath = realpath($path);
+ $this->assertFileExists($realPath);
+
+ $response = new BinaryFileResponse($realPath, 200, array('Content-Type' => 'application/octet-stream'));
+ $response->deleteFileAfterSend(true);
+
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertFileNotExists($path);
+ }
+
+ public function testAcceptRangeOnUnsafeMethods()
+ {
+ $request = Request::create('/', 'POST');
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));
+ $response->prepare($request);
+
+ $this->assertEquals('none', $response->headers->get('Accept-Ranges'));
+ }
+
+ public function testAcceptRangeNotOverriden()
+ {
+ $request = Request::create('/', 'POST');
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'));
+ $response->headers->set('Accept-Ranges', 'foo');
+ $response->prepare($request);
+
+ $this->assertEquals('foo', $response->headers->get('Accept-Ranges'));
+ }
+
+ public function getSampleXAccelMappings()
+ {
+ return array(
+ array('/var/www/var/www/files/foo.txt', '/var/www/=/files/', '/files/var/www/files/foo.txt'),
+ array('/home/foo/bar.txt', '/var/www/=/files/,/home/foo/=/baz/', '/baz/bar.txt'),
+ );
+ }
+
+ public function testStream()
+ {
+ $request = Request::create('/');
+ $response = new BinaryFileResponse(new Stream(__DIR__.'/../README.md'), 200, array('Content-Type' => 'text/plain'));
+ $response->prepare($request);
+
+ $this->assertNull($response->headers->get('Content-Length'));
+ }
+
+ protected function provideResponse()
+ {
+ return new BinaryFileResponse(__DIR__.'/../README.md', 200, array('Content-Type' => 'application/octet-stream'));
+ }
+
+ public static function tearDownAfterClass()
+ {
+ $path = __DIR__.'/../Fixtures/to_delete';
+ if (file_exists($path)) {
+ @unlink($path);
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/CookieTest.php b/assets/php/vendor/symfony/http-foundation/Tests/CookieTest.php
new file mode 100644
index 0000000..070b7dd
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/CookieTest.php
@@ -0,0 +1,223 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Cookie;
+
+/**
+ * CookieTest.
+ *
+ * @author John Kary
+ * @author Hugo Hamon
+ *
+ * @group time-sensitive
+ */
+class CookieTest extends TestCase
+{
+ public function invalidNames()
+ {
+ return array(
+ array(''),
+ array(',MyName'),
+ array(';MyName'),
+ array(' MyName'),
+ array("\tMyName"),
+ array("\rMyName"),
+ array("\nMyName"),
+ array("\013MyName"),
+ array("\014MyName"),
+ );
+ }
+
+ /**
+ * @dataProvider invalidNames
+ * @expectedException \InvalidArgumentException
+ */
+ public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name)
+ {
+ new Cookie($name);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testInvalidExpiration()
+ {
+ new Cookie('MyCookie', 'foo', 'bar');
+ }
+
+ public function testNegativeExpirationIsNotPossible()
+ {
+ $cookie = new Cookie('foo', 'bar', -100);
+
+ $this->assertSame(0, $cookie->getExpiresTime());
+ }
+
+ public function testGetValue()
+ {
+ $value = 'MyValue';
+ $cookie = new Cookie('MyCookie', $value);
+
+ $this->assertSame($value, $cookie->getValue(), '->getValue() returns the proper value');
+ }
+
+ public function testGetPath()
+ {
+ $cookie = new Cookie('foo', 'bar');
+
+ $this->assertSame('/', $cookie->getPath(), '->getPath() returns / as the default path');
+ }
+
+ public function testGetExpiresTime()
+ {
+ $cookie = new Cookie('foo', 'bar');
+
+ $this->assertEquals(0, $cookie->getExpiresTime(), '->getExpiresTime() returns the default expire date');
+
+ $cookie = new Cookie('foo', 'bar', $expire = time() + 3600);
+
+ $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+ }
+
+ public function testGetExpiresTimeIsCastToInt()
+ {
+ $cookie = new Cookie('foo', 'bar', 3600.9);
+
+ $this->assertSame(3600, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date as an integer');
+ }
+
+ public function testConstructorWithDateTime()
+ {
+ $expire = new \DateTime();
+ $cookie = new Cookie('foo', 'bar', $expire);
+
+ $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+ }
+
+ /**
+ * @requires PHP 5.5
+ */
+ public function testConstructorWithDateTimeImmutable()
+ {
+ $expire = new \DateTimeImmutable();
+ $cookie = new Cookie('foo', 'bar', $expire);
+
+ $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+ }
+
+ public function testGetExpiresTimeWithStringValue()
+ {
+ $value = '+1 day';
+ $cookie = new Cookie('foo', 'bar', $value);
+ $expire = strtotime($value);
+
+ $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date', 1);
+ }
+
+ public function testGetDomain()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com');
+
+ $this->assertEquals('.myfoodomain.com', $cookie->getDomain(), '->getDomain() returns the domain name on which the cookie is valid');
+ }
+
+ public function testIsSecure()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', true);
+
+ $this->assertTrue($cookie->isSecure(), '->isSecure() returns whether the cookie is transmitted over HTTPS');
+ }
+
+ public function testIsHttpOnly()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', false, true);
+
+ $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns whether the cookie is only transmitted over HTTP');
+ }
+
+ public function testCookieIsNotCleared()
+ {
+ $cookie = new Cookie('foo', 'bar', time() + 3600 * 24);
+
+ $this->assertFalse($cookie->isCleared(), '->isCleared() returns false if the cookie did not expire yet');
+ }
+
+ public function testCookieIsCleared()
+ {
+ $cookie = new Cookie('foo', 'bar', time() - 20);
+
+ $this->assertTrue($cookie->isCleared(), '->isCleared() returns true if the cookie has expired');
+ }
+
+ public function testToString()
+ {
+ $cookie = new Cookie('foo', 'bar', $expire = strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
+ $this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie');
+
+ $cookie = new Cookie('foo', 'bar with white spaces', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
+ $this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)');
+
+ $cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com');
+ $this->assertEquals('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', $expire = time() - 31536001).'; max-age='.($expire - time()).'; path=/admin/; domain=.myfoodomain.com; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
+
+ $cookie = new Cookie('foo', 'bar', 0, '/', '');
+ $this->assertEquals('foo=bar; path=/; httponly', (string) $cookie);
+ }
+
+ public function testRawCookie()
+ {
+ $cookie = new Cookie('foo', 'b a r', 0, '/', null, false, false);
+ $this->assertFalse($cookie->isRaw());
+ $this->assertEquals('foo=b%20a%20r; path=/', (string) $cookie);
+
+ $cookie = new Cookie('foo', 'b+a+r', 0, '/', null, false, false, true);
+ $this->assertTrue($cookie->isRaw());
+ $this->assertEquals('foo=b+a+r; path=/', (string) $cookie);
+ }
+
+ public function testGetMaxAge()
+ {
+ $cookie = new Cookie('foo', 'bar');
+ $this->assertEquals(0, $cookie->getMaxAge());
+
+ $cookie = new Cookie('foo', 'bar', $expire = time() + 100);
+ $this->assertEquals($expire - time(), $cookie->getMaxAge());
+
+ $cookie = new Cookie('foo', 'bar', $expire = time() - 100);
+ $this->assertEquals($expire - time(), $cookie->getMaxAge());
+ }
+
+ public function testFromString()
+ {
+ $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly');
+ $this->assertEquals(new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true, true, true), $cookie);
+
+ $cookie = Cookie::fromString('foo=bar', true);
+ $this->assertEquals(new Cookie('foo', 'bar', 0, '/', null, false, false), $cookie);
+ }
+
+ public function testFromStringWithHttpOnly()
+ {
+ $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly');
+ $this->assertTrue($cookie->isHttpOnly());
+
+ $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure');
+ $this->assertFalse($cookie->isHttpOnly());
+ }
+
+ public function testSameSiteAttributeIsCaseInsensitive()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', null, false, true, false, 'Lax');
+ $this->assertEquals('lax', $cookie->getSameSite());
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php
new file mode 100644
index 0000000..1152e46
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\HttpFoundation\ExpressionRequestMatcher;
+use Symfony\Component\HttpFoundation\Request;
+
+class ExpressionRequestMatcherTest extends TestCase
+{
+ /**
+ * @expectedException \LogicException
+ */
+ public function testWhenNoExpressionIsSet()
+ {
+ $expressionRequestMatcher = new ExpressionRequestMatcher();
+ $expressionRequestMatcher->matches(new Request());
+ }
+
+ /**
+ * @dataProvider provideExpressions
+ */
+ public function testMatchesWhenParentMatchesIsTrue($expression, $expected)
+ {
+ $request = Request::create('/foo');
+ $expressionRequestMatcher = new ExpressionRequestMatcher();
+
+ $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression);
+ $this->assertSame($expected, $expressionRequestMatcher->matches($request));
+ }
+
+ /**
+ * @dataProvider provideExpressions
+ */
+ public function testMatchesWhenParentMatchesIsFalse($expression)
+ {
+ $request = Request::create('/foo');
+ $request->attributes->set('foo', 'foo');
+ $expressionRequestMatcher = new ExpressionRequestMatcher();
+ $expressionRequestMatcher->matchAttribute('foo', 'bar');
+
+ $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression);
+ $this->assertFalse($expressionRequestMatcher->matches($request));
+ }
+
+ public function provideExpressions()
+ {
+ return array(
+ array('request.getMethod() == method', true),
+ array('request.getPathInfo() == path', true),
+ array('request.getHost() == host', true),
+ array('request.getClientIp() == ip', true),
+ array('request.attributes.all() == attributes', true),
+ array('request.getMethod() == method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip && request.attributes.all() == attributes', true),
+ array('request.getMethod() != method', false),
+ array('request.getMethod() != method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip && request.attributes.all() == attributes', false),
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/FakeFile.php b/assets/php/vendor/symfony/http-foundation/Tests/File/FakeFile.php
new file mode 100644
index 0000000..c415989
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/File/FakeFile.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use Symfony\Component\HttpFoundation\File\File as OrigFile;
+
+class FakeFile extends OrigFile
+{
+ private $realpath;
+
+ public function __construct($realpath, $path)
+ {
+ $this->realpath = $realpath;
+ parent::__construct($path, false);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function getRealpath()
+ {
+ return $this->realpath;
+ }
+
+ public function getSize()
+ {
+ return 42;
+ }
+
+ public function getMTime()
+ {
+ return time();
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/FileTest.php b/assets/php/vendor/symfony/http-foundation/Tests/File/FileTest.php
new file mode 100644
index 0000000..dbd9c44
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/File/FileTest.php
@@ -0,0 +1,180 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\File;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+
+class FileTest extends TestCase
+{
+ protected $file;
+
+ public function testGetMimeTypeUsesMimeTypeGuessers()
+ {
+ $file = new File(__DIR__.'/Fixtures/test.gif');
+ $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+
+ MimeTypeGuesser::getInstance()->register($guesser);
+
+ $this->assertEquals('image/gif', $file->getMimeType());
+ }
+
+ public function testGuessExtensionWithoutGuesser()
+ {
+ $file = new File(__DIR__.'/Fixtures/directory/.empty');
+
+ $this->assertNull($file->guessExtension());
+ }
+
+ public function testGuessExtensionIsBasedOnMimeType()
+ {
+ $file = new File(__DIR__.'/Fixtures/test');
+ $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+
+ MimeTypeGuesser::getInstance()->register($guesser);
+
+ $this->assertEquals('gif', $file->guessExtension());
+ }
+
+ /**
+ * @requires extension fileinfo
+ */
+ public function testGuessExtensionWithReset()
+ {
+ $file = new File(__DIR__.'/Fixtures/other-file.example');
+ $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+ MimeTypeGuesser::getInstance()->register($guesser);
+
+ $this->assertEquals('gif', $file->guessExtension());
+
+ MimeTypeGuesser::reset();
+
+ $this->assertNull($file->guessExtension());
+ }
+
+ public function testConstructWhenFileNotExists()
+ {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+ new File(__DIR__.'/Fixtures/not_here');
+ }
+
+ public function testMove()
+ {
+ $path = __DIR__.'/Fixtures/test.copy.gif';
+ $targetDir = __DIR__.'/Fixtures/directory';
+ $targetPath = $targetDir.'/test.copy.gif';
+ @unlink($path);
+ @unlink($targetPath);
+ copy(__DIR__.'/Fixtures/test.gif', $path);
+
+ $file = new File($path);
+ $movedFile = $file->move($targetDir);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile);
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($path);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($targetPath);
+ }
+
+ public function testMoveWithNewName()
+ {
+ $path = __DIR__.'/Fixtures/test.copy.gif';
+ $targetDir = __DIR__.'/Fixtures/directory';
+ $targetPath = $targetDir.'/test.newname.gif';
+ @unlink($path);
+ @unlink($targetPath);
+ copy(__DIR__.'/Fixtures/test.gif', $path);
+
+ $file = new File($path);
+ $movedFile = $file->move($targetDir, 'test.newname.gif');
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($path);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($targetPath);
+ }
+
+ public function getFilenameFixtures()
+ {
+ return array(
+ array('original.gif', 'original.gif'),
+ array('..\\..\\original.gif', 'original.gif'),
+ array('../../original.gif', 'original.gif'),
+ array('файлfile.gif', 'файлfile.gif'),
+ array('..\\..\\файлfile.gif', 'файлfile.gif'),
+ array('../../файлfile.gif', 'файлfile.gif'),
+ );
+ }
+
+ /**
+ * @dataProvider getFilenameFixtures
+ */
+ public function testMoveWithNonLatinName($filename, $sanitizedFilename)
+ {
+ $path = __DIR__.'/Fixtures/'.$sanitizedFilename;
+ $targetDir = __DIR__.'/Fixtures/directory/';
+ $targetPath = $targetDir.$sanitizedFilename;
+ @unlink($path);
+ @unlink($targetPath);
+ copy(__DIR__.'/Fixtures/test.gif', $path);
+
+ $file = new File($path);
+ $movedFile = $file->move($targetDir, $filename);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile);
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($path);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($targetPath);
+ }
+
+ public function testMoveToAnUnexistentDirectory()
+ {
+ $sourcePath = __DIR__.'/Fixtures/test.copy.gif';
+ $targetDir = __DIR__.'/Fixtures/directory/sub';
+ $targetPath = $targetDir.'/test.copy.gif';
+ @unlink($sourcePath);
+ @unlink($targetPath);
+ @rmdir($targetDir);
+ copy(__DIR__.'/Fixtures/test.gif', $sourcePath);
+
+ $file = new File($sourcePath);
+ $movedFile = $file->move($targetDir);
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($sourcePath);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($sourcePath);
+ @unlink($targetPath);
+ @rmdir($targetDir);
+ }
+
+ protected function createMockGuesser($path, $mimeType)
+ {
+ $guesser = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface')->getMock();
+ $guesser
+ ->expects($this->once())
+ ->method('guess')
+ ->with($this->equalTo($path))
+ ->will($this->returnValue($mimeType))
+ ;
+
+ return $guesser;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension
new file mode 100644
index 0000000..4d1ae35
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension
@@ -0,0 +1 @@
+f
\ No newline at end of file
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example
new file mode 100644
index 0000000..e69de29
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test
new file mode 100644
index 0000000..b636f4b
Binary files /dev/null and b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test differ
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif
new file mode 100644
index 0000000..b636f4b
Binary files /dev/null and b/assets/php/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif differ
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php b/assets/php/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php
new file mode 100644
index 0000000..b3f1f02
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File\MimeType;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser;
+
+/**
+ * @requires extension fileinfo
+ */
+class MimeTypeTest extends TestCase
+{
+ protected $path;
+
+ public function testGuessImageWithoutExtension()
+ {
+ $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test'));
+ }
+
+ public function testGuessImageWithDirectory()
+ {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+ MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/directory');
+ }
+
+ public function testGuessImageWithFileBinaryMimeTypeGuesser()
+ {
+ $guesser = MimeTypeGuesser::getInstance();
+ $guesser->register(new FileBinaryMimeTypeGuesser());
+ $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test'));
+ }
+
+ public function testGuessImageWithKnownExtension()
+ {
+ $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif'));
+ }
+
+ public function testGuessFileWithUnknownExtension()
+ {
+ $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension'));
+ }
+
+ public function testGuessWithIncorrectPath()
+ {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+ MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/not_here');
+ }
+
+ public function testGuessWithNonReadablePath()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Can not verify chmod operations on Windows');
+ }
+
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+
+ $path = __DIR__.'/../Fixtures/to_delete';
+ touch($path);
+ @chmod($path, 0333);
+
+ if ('0333' == substr(sprintf('%o', fileperms($path)), -4)) {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException');
+ MimeTypeGuesser::getInstance()->guess($path);
+ } else {
+ $this->markTestSkipped('Can not verify chmod operations, change of file permissions failed');
+ }
+ }
+
+ public static function tearDownAfterClass()
+ {
+ $path = __DIR__.'/../Fixtures/to_delete';
+ if (file_exists($path)) {
+ @chmod($path, 0666);
+ @unlink($path);
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php b/assets/php/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php
new file mode 100644
index 0000000..36f122f
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php
@@ -0,0 +1,273 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+class UploadedFileTest extends TestCase
+{
+ protected function setUp()
+ {
+ if (!ini_get('file_uploads')) {
+ $this->markTestSkipped('file_uploads is disabled in php.ini');
+ }
+ }
+
+ public function testConstructWhenFileNotExists()
+ {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+ new UploadedFile(
+ __DIR__.'/Fixtures/not_here',
+ 'original.gif',
+ null
+ );
+ }
+
+ public function testFileUploadsWithNoMimeType()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ UPLOAD_ERR_OK
+ );
+
+ $this->assertEquals('application/octet-stream', $file->getClientMimeType());
+
+ if (extension_loaded('fileinfo')) {
+ $this->assertEquals('image/gif', $file->getMimeType());
+ }
+ }
+
+ public function testFileUploadsWithUnknownMimeType()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/.unknownextension',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/.unknownextension'),
+ UPLOAD_ERR_OK
+ );
+
+ $this->assertEquals('application/octet-stream', $file->getClientMimeType());
+ }
+
+ public function testGuessClientExtension()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('gif', $file->guessClientExtension());
+ }
+
+ public function testGuessClientExtensionWithIncorrectMimeType()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/jpeg',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('jpeg', $file->guessClientExtension());
+ }
+
+ public function testErrorIsOkByDefault()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals(UPLOAD_ERR_OK, $file->getError());
+ }
+
+ public function testGetClientOriginalName()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('original.gif', $file->getClientOriginalName());
+ }
+
+ public function testGetClientOriginalExtension()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('gif', $file->getClientOriginalExtension());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException
+ */
+ public function testMoveLocalFileIsNotAllowed()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ UPLOAD_ERR_OK
+ );
+
+ $movedFile = $file->move(__DIR__.'/Fixtures/directory');
+ }
+
+ public function testMoveLocalFileIsAllowedInTestMode()
+ {
+ $path = __DIR__.'/Fixtures/test.copy.gif';
+ $targetDir = __DIR__.'/Fixtures/directory';
+ $targetPath = $targetDir.'/test.copy.gif';
+ @unlink($path);
+ @unlink($targetPath);
+ copy(__DIR__.'/Fixtures/test.gif', $path);
+
+ $file = new UploadedFile(
+ $path,
+ 'original.gif',
+ 'image/gif',
+ filesize($path),
+ UPLOAD_ERR_OK,
+ true
+ );
+
+ $movedFile = $file->move(__DIR__.'/Fixtures/directory');
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($path);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($targetPath);
+ }
+
+ public function testGetClientOriginalNameSanitizeFilename()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ '../../original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('original.gif', $file->getClientOriginalName());
+ }
+
+ public function testGetSize()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals(filesize(__DIR__.'/Fixtures/test.gif'), $file->getSize());
+
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test',
+ 'original.gif',
+ 'image/gif'
+ );
+
+ $this->assertEquals(filesize(__DIR__.'/Fixtures/test'), $file->getSize());
+ }
+
+ public function testGetExtension()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null
+ );
+
+ $this->assertEquals('gif', $file->getExtension());
+ }
+
+ public function testIsValid()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ UPLOAD_ERR_OK,
+ true
+ );
+
+ $this->assertTrue($file->isValid());
+ }
+
+ /**
+ * @dataProvider uploadedFileErrorProvider
+ */
+ public function testIsInvalidOnUploadError($error)
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ $error
+ );
+
+ $this->assertFalse($file->isValid());
+ }
+
+ public function uploadedFileErrorProvider()
+ {
+ return array(
+ array(UPLOAD_ERR_INI_SIZE),
+ array(UPLOAD_ERR_FORM_SIZE),
+ array(UPLOAD_ERR_PARTIAL),
+ array(UPLOAD_ERR_NO_TMP_DIR),
+ array(UPLOAD_ERR_EXTENSION),
+ );
+ }
+
+ public function testIsInvalidIfNotHttpUpload()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ UPLOAD_ERR_OK
+ );
+
+ $this->assertFalse($file->isValid());
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/FileBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/FileBagTest.php
new file mode 100644
index 0000000..b1bbba0
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/FileBagTest.php
@@ -0,0 +1,175 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\FileBag;
+
+/**
+ * FileBagTest.
+ *
+ * @author Fabien Potencier
+ * @author Bulat Shakirzyanov
+ */
+class FileBagTest extends TestCase
+{
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testFileMustBeAnArrayOrUploadedFile()
+ {
+ new FileBag(array('file' => 'foo'));
+ }
+
+ public function testShouldConvertsUploadedFiles()
+ {
+ $tmpFile = $this->createTempFile();
+ $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+ $bag = new FileBag(array('file' => array(
+ 'name' => basename($tmpFile),
+ 'type' => 'text/plain',
+ 'tmp_name' => $tmpFile,
+ 'error' => 0,
+ 'size' => 100,
+ )));
+
+ $this->assertEquals($file, $bag->get('file'));
+ }
+
+ public function testShouldSetEmptyUploadedFilesToNull()
+ {
+ $bag = new FileBag(array('file' => array(
+ 'name' => '',
+ 'type' => '',
+ 'tmp_name' => '',
+ 'error' => UPLOAD_ERR_NO_FILE,
+ 'size' => 0,
+ )));
+
+ $this->assertNull($bag->get('file'));
+ }
+
+ public function testShouldRemoveEmptyUploadedFilesForMultiUpload()
+ {
+ $bag = new FileBag(array('files' => array(
+ 'name' => array(''),
+ 'type' => array(''),
+ 'tmp_name' => array(''),
+ 'error' => array(UPLOAD_ERR_NO_FILE),
+ 'size' => array(0),
+ )));
+
+ $this->assertSame(array(), $bag->get('files'));
+ }
+
+ public function testShouldNotRemoveEmptyUploadedFilesForAssociativeArray()
+ {
+ $bag = new FileBag(array('files' => array(
+ 'name' => array('file1' => ''),
+ 'type' => array('file1' => ''),
+ 'tmp_name' => array('file1' => ''),
+ 'error' => array('file1' => UPLOAD_ERR_NO_FILE),
+ 'size' => array('file1' => 0),
+ )));
+
+ $this->assertSame(array('file1' => null), $bag->get('files'));
+ }
+
+ public function testShouldConvertUploadedFilesWithPhpBug()
+ {
+ $tmpFile = $this->createTempFile();
+ $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+ $bag = new FileBag(array(
+ 'child' => array(
+ 'name' => array(
+ 'file' => basename($tmpFile),
+ ),
+ 'type' => array(
+ 'file' => 'text/plain',
+ ),
+ 'tmp_name' => array(
+ 'file' => $tmpFile,
+ ),
+ 'error' => array(
+ 'file' => 0,
+ ),
+ 'size' => array(
+ 'file' => 100,
+ ),
+ ),
+ ));
+
+ $files = $bag->all();
+ $this->assertEquals($file, $files['child']['file']);
+ }
+
+ public function testShouldConvertNestedUploadedFilesWithPhpBug()
+ {
+ $tmpFile = $this->createTempFile();
+ $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+ $bag = new FileBag(array(
+ 'child' => array(
+ 'name' => array(
+ 'sub' => array('file' => basename($tmpFile)),
+ ),
+ 'type' => array(
+ 'sub' => array('file' => 'text/plain'),
+ ),
+ 'tmp_name' => array(
+ 'sub' => array('file' => $tmpFile),
+ ),
+ 'error' => array(
+ 'sub' => array('file' => 0),
+ ),
+ 'size' => array(
+ 'sub' => array('file' => 100),
+ ),
+ ),
+ ));
+
+ $files = $bag->all();
+ $this->assertEquals($file, $files['child']['sub']['file']);
+ }
+
+ public function testShouldNotConvertNestedUploadedFiles()
+ {
+ $tmpFile = $this->createTempFile();
+ $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+ $bag = new FileBag(array('image' => array('file' => $file)));
+
+ $files = $bag->all();
+ $this->assertEquals($file, $files['image']['file']);
+ }
+
+ protected function createTempFile()
+ {
+ return tempnam(sys_get_temp_dir().'/form_test', 'FormTest');
+ }
+
+ protected function setUp()
+ {
+ mkdir(sys_get_temp_dir().'/form_test', 0777, true);
+ }
+
+ protected function tearDown()
+ {
+ foreach (glob(sys_get_temp_dir().'/form_test/*') as $file) {
+ unlink($file);
+ }
+
+ rmdir(sys_get_temp_dir().'/form_test');
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/HeaderBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/HeaderBagTest.php
new file mode 100644
index 0000000..6d19ceb
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/HeaderBagTest.php
@@ -0,0 +1,205 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\HeaderBag;
+
+class HeaderBagTest extends TestCase
+{
+ public function testConstructor()
+ {
+ $bag = new HeaderBag(array('foo' => 'bar'));
+ $this->assertTrue($bag->has('foo'));
+ }
+
+ public function testToStringNull()
+ {
+ $bag = new HeaderBag();
+ $this->assertEquals('', $bag->__toString());
+ }
+
+ public function testToStringNotNull()
+ {
+ $bag = new HeaderBag(array('foo' => 'bar'));
+ $this->assertEquals("Foo: bar\r\n", $bag->__toString());
+ }
+
+ public function testKeys()
+ {
+ $bag = new HeaderBag(array('foo' => 'bar'));
+ $keys = $bag->keys();
+ $this->assertEquals('foo', $keys[0]);
+ }
+
+ public function testGetDate()
+ {
+ $bag = new HeaderBag(array('foo' => 'Tue, 4 Sep 2012 20:00:00 +0200'));
+ $headerDate = $bag->getDate('foo');
+ $this->assertInstanceOf('DateTime', $headerDate);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testGetDateException()
+ {
+ $bag = new HeaderBag(array('foo' => 'Tue'));
+ $headerDate = $bag->getDate('foo');
+ }
+
+ public function testGetCacheControlHeader()
+ {
+ $bag = new HeaderBag();
+ $bag->addCacheControlDirective('public', '#a');
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ $this->assertEquals('#a', $bag->getCacheControlDirective('public'));
+ }
+
+ public function testAll()
+ {
+ $bag = new HeaderBag(array('foo' => 'bar'));
+ $this->assertEquals(array('foo' => array('bar')), $bag->all(), '->all() gets all the input');
+
+ $bag = new HeaderBag(array('FOO' => 'BAR'));
+ $this->assertEquals(array('foo' => array('BAR')), $bag->all(), '->all() gets all the input key are lower case');
+ }
+
+ public function testReplace()
+ {
+ $bag = new HeaderBag(array('foo' => 'bar'));
+
+ $bag->replace(array('NOPE' => 'BAR'));
+ $this->assertEquals(array('nope' => array('BAR')), $bag->all(), '->replace() replaces the input with the argument');
+ $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input');
+ }
+
+ public function testGet()
+ {
+ $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz'));
+ $this->assertEquals('bar', $bag->get('foo'), '->get return current value');
+ $this->assertEquals('bar', $bag->get('FoO'), '->get key in case insensitive');
+ $this->assertEquals(array('bar'), $bag->get('foo', 'nope', false), '->get return the value as array');
+
+ // defaults
+ $this->assertNull($bag->get('none'), '->get unknown values returns null');
+ $this->assertEquals('default', $bag->get('none', 'default'), '->get unknown values returns default');
+ $this->assertEquals(array('default'), $bag->get('none', 'default', false), '->get unknown values returns default as array');
+
+ $bag->set('foo', 'bor', false);
+ $this->assertEquals('bar', $bag->get('foo'), '->get return first value');
+ $this->assertEquals(array('bar', 'bor'), $bag->get('foo', 'nope', false), '->get return all values as array');
+ }
+
+ public function testSetAssociativeArray()
+ {
+ $bag = new HeaderBag();
+ $bag->set('foo', array('bad-assoc-index' => 'value'));
+ $this->assertSame('value', $bag->get('foo'));
+ $this->assertEquals(array('value'), $bag->get('foo', 'nope', false), 'assoc indices of multi-valued headers are ignored');
+ }
+
+ public function testContains()
+ {
+ $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz'));
+ $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value');
+ $this->assertTrue($bag->contains('fuzz', 'bizz'), '->contains second value');
+ $this->assertFalse($bag->contains('nope', 'nope'), '->contains unknown value');
+ $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value');
+
+ // Multiple values
+ $bag->set('foo', 'bor', false);
+ $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value');
+ $this->assertTrue($bag->contains('foo', 'bor'), '->contains second value');
+ $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value');
+ }
+
+ public function testCacheControlDirectiveAccessors()
+ {
+ $bag = new HeaderBag();
+ $bag->addCacheControlDirective('public');
+
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ $this->assertTrue($bag->getCacheControlDirective('public'));
+ $this->assertEquals('public', $bag->get('cache-control'));
+
+ $bag->addCacheControlDirective('max-age', 10);
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+ $this->assertEquals('max-age=10, public', $bag->get('cache-control'));
+
+ $bag->removeCacheControlDirective('max-age');
+ $this->assertFalse($bag->hasCacheControlDirective('max-age'));
+ }
+
+ public function testCacheControlDirectiveParsing()
+ {
+ $bag = new HeaderBag(array('cache-control' => 'public, max-age=10'));
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ $this->assertTrue($bag->getCacheControlDirective('public'));
+
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+
+ $bag->addCacheControlDirective('s-maxage', 100);
+ $this->assertEquals('max-age=10, public, s-maxage=100', $bag->get('cache-control'));
+ }
+
+ public function testCacheControlDirectiveParsingQuotedZero()
+ {
+ $bag = new HeaderBag(array('cache-control' => 'max-age="0"'));
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(0, $bag->getCacheControlDirective('max-age'));
+ }
+
+ public function testCacheControlDirectiveOverrideWithReplace()
+ {
+ $bag = new HeaderBag(array('cache-control' => 'private, max-age=100'));
+ $bag->replace(array('cache-control' => 'public, max-age=10'));
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ $this->assertTrue($bag->getCacheControlDirective('public'));
+
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+ }
+
+ public function testCacheControlClone()
+ {
+ $headers = array('foo' => 'bar');
+ $bag1 = new HeaderBag($headers);
+ $bag2 = new HeaderBag($bag1->all());
+
+ $this->assertEquals($bag1->all(), $bag2->all());
+ }
+
+ public function testGetIterator()
+ {
+ $headers = array('foo' => 'bar', 'hello' => 'world', 'third' => 'charm');
+ $headerBag = new HeaderBag($headers);
+
+ $i = 0;
+ foreach ($headerBag as $key => $val) {
+ ++$i;
+ $this->assertEquals(array($headers[$key]), $val);
+ }
+
+ $this->assertEquals(count($headers), $i);
+ }
+
+ public function testCount()
+ {
+ $headers = array('foo' => 'bar', 'HELLO' => 'WORLD');
+ $headerBag = new HeaderBag($headers);
+
+ $this->assertCount(count($headers), $headerBag);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/IpUtilsTest.php b/assets/php/vendor/symfony/http-foundation/Tests/IpUtilsTest.php
new file mode 100644
index 0000000..7a93f99
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/IpUtilsTest.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\IpUtils;
+
+class IpUtilsTest extends TestCase
+{
+ /**
+ * @dataProvider getIpv4Data
+ */
+ public function testIpv4($matches, $remoteAddr, $cidr)
+ {
+ $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr));
+ }
+
+ public function getIpv4Data()
+ {
+ return array(
+ array(true, '192.168.1.1', '192.168.1.1'),
+ array(true, '192.168.1.1', '192.168.1.1/1'),
+ array(true, '192.168.1.1', '192.168.1.0/24'),
+ array(false, '192.168.1.1', '1.2.3.4/1'),
+ array(false, '192.168.1.1', '192.168.1.1/33'), // invalid subnet
+ array(true, '192.168.1.1', array('1.2.3.4/1', '192.168.1.0/24')),
+ array(true, '192.168.1.1', array('192.168.1.0/24', '1.2.3.4/1')),
+ array(false, '192.168.1.1', array('1.2.3.4/1', '4.3.2.1/1')),
+ array(true, '1.2.3.4', '0.0.0.0/0'),
+ array(true, '1.2.3.4', '192.168.1.0/0'),
+ array(false, '1.2.3.4', '256.256.256/0'), // invalid CIDR notation
+ array(false, 'an_invalid_ip', '192.168.1.0/24'),
+ );
+ }
+
+ /**
+ * @dataProvider getIpv6Data
+ */
+ public function testIpv6($matches, $remoteAddr, $cidr)
+ {
+ if (!defined('AF_INET6')) {
+ $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".');
+ }
+
+ $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr));
+ }
+
+ public function getIpv6Data()
+ {
+ return array(
+ array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
+ array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
+ array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'),
+ array(true, '0:0:0:0:0:0:0:1', '::1'),
+ array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'),
+ array(true, '0:0:603:0:396e:4789:8e99:0001', '::/0'),
+ array(true, '0:0:603:0:396e:4789:8e99:0001', '2a01:198:603:0::/0'),
+ array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '2a01:198:603:0::/65')),
+ array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('2a01:198:603:0::/65', '::1')),
+ array(false, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '1a01:198:603:0::/65')),
+ array(false, '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2', '::1'),
+ array(false, '2a01:198:603:0:396e:4789:8e99:890f', 'unknown'),
+ );
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @requires extension sockets
+ */
+ public function testAnIpv6WithOptionDisabledIpv6()
+ {
+ if (defined('AF_INET6')) {
+ $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".');
+ }
+
+ IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65');
+ }
+
+ /**
+ * @dataProvider invalidIpAddressData
+ */
+ public function testInvalidIpAddressesDoNotMatch($requestIp, $proxyIp)
+ {
+ $this->assertFalse(IpUtils::checkIp4($requestIp, $proxyIp));
+ }
+
+ public function invalidIpAddressData()
+ {
+ return array(
+ 'invalid proxy wildcard' => array('192.168.20.13', '*'),
+ 'invalid proxy missing netmask' => array('192.168.20.13', '0.0.0.0'),
+ 'invalid request IP with invalid proxy wildcard' => array('0.0.0.0', '*'),
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/JsonResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/JsonResponseTest.php
new file mode 100644
index 0000000..201839f
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/JsonResponseTest.php
@@ -0,0 +1,257 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+class JsonResponseTest extends TestCase
+{
+ public function testConstructorEmptyCreatesJsonObject()
+ {
+ $response = new JsonResponse();
+ $this->assertSame('{}', $response->getContent());
+ }
+
+ public function testConstructorWithArrayCreatesJsonArray()
+ {
+ $response = new JsonResponse(array(0, 1, 2, 3));
+ $this->assertSame('[0,1,2,3]', $response->getContent());
+ }
+
+ public function testConstructorWithAssocArrayCreatesJsonObject()
+ {
+ $response = new JsonResponse(array('foo' => 'bar'));
+ $this->assertSame('{"foo":"bar"}', $response->getContent());
+ }
+
+ public function testConstructorWithSimpleTypes()
+ {
+ $response = new JsonResponse('foo');
+ $this->assertSame('"foo"', $response->getContent());
+
+ $response = new JsonResponse(0);
+ $this->assertSame('0', $response->getContent());
+
+ $response = new JsonResponse(0.1);
+ $this->assertSame('0.1', $response->getContent());
+
+ $response = new JsonResponse(true);
+ $this->assertSame('true', $response->getContent());
+ }
+
+ public function testConstructorWithCustomStatus()
+ {
+ $response = new JsonResponse(array(), 202);
+ $this->assertSame(202, $response->getStatusCode());
+ }
+
+ public function testConstructorAddsContentTypeHeader()
+ {
+ $response = new JsonResponse();
+ $this->assertSame('application/json', $response->headers->get('Content-Type'));
+ }
+
+ public function testConstructorWithCustomHeaders()
+ {
+ $response = new JsonResponse(array(), 200, array('ETag' => 'foo'));
+ $this->assertSame('application/json', $response->headers->get('Content-Type'));
+ $this->assertSame('foo', $response->headers->get('ETag'));
+ }
+
+ public function testConstructorWithCustomContentType()
+ {
+ $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json');
+
+ $response = new JsonResponse(array(), 200, $headers);
+ $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type'));
+ }
+
+ public function testSetJson()
+ {
+ $response = new JsonResponse('1', 200, array(), true);
+ $this->assertEquals('1', $response->getContent());
+
+ $response = new JsonResponse('[1]', 200, array(), true);
+ $this->assertEquals('[1]', $response->getContent());
+
+ $response = new JsonResponse(null, 200, array());
+ $response->setJson('true');
+ $this->assertEquals('true', $response->getContent());
+ }
+
+ public function testCreate()
+ {
+ $response = JsonResponse::create(array('foo' => 'bar'), 204);
+
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertEquals('{"foo":"bar"}', $response->getContent());
+ $this->assertEquals(204, $response->getStatusCode());
+ }
+
+ public function testStaticCreateEmptyJsonObject()
+ {
+ $response = JsonResponse::create();
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('{}', $response->getContent());
+ }
+
+ public function testStaticCreateJsonArray()
+ {
+ $response = JsonResponse::create(array(0, 1, 2, 3));
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('[0,1,2,3]', $response->getContent());
+ }
+
+ public function testStaticCreateJsonObject()
+ {
+ $response = JsonResponse::create(array('foo' => 'bar'));
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('{"foo":"bar"}', $response->getContent());
+ }
+
+ public function testStaticCreateWithSimpleTypes()
+ {
+ $response = JsonResponse::create('foo');
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('"foo"', $response->getContent());
+
+ $response = JsonResponse::create(0);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('0', $response->getContent());
+
+ $response = JsonResponse::create(0.1);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('0.1', $response->getContent());
+
+ $response = JsonResponse::create(true);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('true', $response->getContent());
+ }
+
+ public function testStaticCreateWithCustomStatus()
+ {
+ $response = JsonResponse::create(array(), 202);
+ $this->assertSame(202, $response->getStatusCode());
+ }
+
+ public function testStaticCreateAddsContentTypeHeader()
+ {
+ $response = JsonResponse::create();
+ $this->assertSame('application/json', $response->headers->get('Content-Type'));
+ }
+
+ public function testStaticCreateWithCustomHeaders()
+ {
+ $response = JsonResponse::create(array(), 200, array('ETag' => 'foo'));
+ $this->assertSame('application/json', $response->headers->get('Content-Type'));
+ $this->assertSame('foo', $response->headers->get('ETag'));
+ }
+
+ public function testStaticCreateWithCustomContentType()
+ {
+ $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json');
+
+ $response = JsonResponse::create(array(), 200, $headers);
+ $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type'));
+ }
+
+ public function testSetCallback()
+ {
+ $response = JsonResponse::create(array('foo' => 'bar'))->setCallback('callback');
+
+ $this->assertEquals('/**/callback({"foo":"bar"});', $response->getContent());
+ $this->assertEquals('text/javascript', $response->headers->get('Content-Type'));
+ }
+
+ public function testJsonEncodeFlags()
+ {
+ $response = new JsonResponse('<>\'&"');
+
+ $this->assertEquals('"\u003C\u003E\u0027\u0026\u0022"', $response->getContent());
+ }
+
+ public function testGetEncodingOptions()
+ {
+ $response = new JsonResponse();
+
+ $this->assertEquals(JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT, $response->getEncodingOptions());
+ }
+
+ public function testSetEncodingOptions()
+ {
+ $response = new JsonResponse();
+ $response->setData(array(array(1, 2, 3)));
+
+ $this->assertEquals('[[1,2,3]]', $response->getContent());
+
+ $response->setEncodingOptions(JSON_FORCE_OBJECT);
+
+ $this->assertEquals('{"0":{"0":1,"1":2,"2":3}}', $response->getContent());
+ }
+
+ public function testItAcceptsJsonAsString()
+ {
+ $response = JsonResponse::fromJsonString('{"foo":"bar"}');
+ $this->assertSame('{"foo":"bar"}', $response->getContent());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetCallbackInvalidIdentifier()
+ {
+ $response = new JsonResponse('foo');
+ $response->setCallback('+invalid');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetContent()
+ {
+ JsonResponse::create("\xB1\x31");
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage This error is expected
+ */
+ public function testSetContentJsonSerializeError()
+ {
+ if (!interface_exists('JsonSerializable', false)) {
+ $this->markTestSkipped('JsonSerializable is required.');
+ }
+
+ $serializable = new JsonSerializableObject();
+
+ JsonResponse::create($serializable);
+ }
+
+ public function testSetComplexCallback()
+ {
+ $response = JsonResponse::create(array('foo' => 'bar'));
+ $response->setCallback('ಠ_ಠ["foo"].bar[0]');
+
+ $this->assertEquals('/**/ಠ_ಠ["foo"].bar[0]({"foo":"bar"});', $response->getContent());
+ }
+}
+
+if (interface_exists('JsonSerializable', false)) {
+ class JsonSerializableObject implements \JsonSerializable
+ {
+ public function jsonSerialize()
+ {
+ throw new \Exception('This error is expected');
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/ParameterBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ParameterBagTest.php
new file mode 100644
index 0000000..ab908d8
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/ParameterBagTest.php
@@ -0,0 +1,194 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ParameterBag;
+
+class ParameterBagTest extends TestCase
+{
+ public function testConstructor()
+ {
+ $this->testAll();
+ }
+
+ public function testAll()
+ {
+ $bag = new ParameterBag(array('foo' => 'bar'));
+ $this->assertEquals(array('foo' => 'bar'), $bag->all(), '->all() gets all the input');
+ }
+
+ public function testKeys()
+ {
+ $bag = new ParameterBag(array('foo' => 'bar'));
+ $this->assertEquals(array('foo'), $bag->keys());
+ }
+
+ public function testAdd()
+ {
+ $bag = new ParameterBag(array('foo' => 'bar'));
+ $bag->add(array('bar' => 'bas'));
+ $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all());
+ }
+
+ public function testRemove()
+ {
+ $bag = new ParameterBag(array('foo' => 'bar'));
+ $bag->add(array('bar' => 'bas'));
+ $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all());
+ $bag->remove('bar');
+ $this->assertEquals(array('foo' => 'bar'), $bag->all());
+ }
+
+ public function testReplace()
+ {
+ $bag = new ParameterBag(array('foo' => 'bar'));
+
+ $bag->replace(array('FOO' => 'BAR'));
+ $this->assertEquals(array('FOO' => 'BAR'), $bag->all(), '->replace() replaces the input with the argument');
+ $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input');
+ }
+
+ public function testGet()
+ {
+ $bag = new ParameterBag(array('foo' => 'bar', 'null' => null));
+
+ $this->assertEquals('bar', $bag->get('foo'), '->get() gets the value of a parameter');
+ $this->assertEquals('default', $bag->get('unknown', 'default'), '->get() returns second argument as default if a parameter is not defined');
+ $this->assertNull($bag->get('null', 'default'), '->get() returns null if null is set');
+ }
+
+ public function testGetDoesNotUseDeepByDefault()
+ {
+ $bag = new ParameterBag(array('foo' => array('bar' => 'moo')));
+
+ $this->assertNull($bag->get('foo[bar]'));
+ }
+
+ public function testSet()
+ {
+ $bag = new ParameterBag(array());
+
+ $bag->set('foo', 'bar');
+ $this->assertEquals('bar', $bag->get('foo'), '->set() sets the value of parameter');
+
+ $bag->set('foo', 'baz');
+ $this->assertEquals('baz', $bag->get('foo'), '->set() overrides previously set parameter');
+ }
+
+ public function testHas()
+ {
+ $bag = new ParameterBag(array('foo' => 'bar'));
+
+ $this->assertTrue($bag->has('foo'), '->has() returns true if a parameter is defined');
+ $this->assertFalse($bag->has('unknown'), '->has() return false if a parameter is not defined');
+ }
+
+ public function testGetAlpha()
+ {
+ $bag = new ParameterBag(array('word' => 'foo_BAR_012'));
+
+ $this->assertEquals('fooBAR', $bag->getAlpha('word'), '->getAlpha() gets only alphabetic characters');
+ $this->assertEquals('', $bag->getAlpha('unknown'), '->getAlpha() returns empty string if a parameter is not defined');
+ }
+
+ public function testGetAlnum()
+ {
+ $bag = new ParameterBag(array('word' => 'foo_BAR_012'));
+
+ $this->assertEquals('fooBAR012', $bag->getAlnum('word'), '->getAlnum() gets only alphanumeric characters');
+ $this->assertEquals('', $bag->getAlnum('unknown'), '->getAlnum() returns empty string if a parameter is not defined');
+ }
+
+ public function testGetDigits()
+ {
+ $bag = new ParameterBag(array('word' => 'foo_BAR_012'));
+
+ $this->assertEquals('012', $bag->getDigits('word'), '->getDigits() gets only digits as string');
+ $this->assertEquals('', $bag->getDigits('unknown'), '->getDigits() returns empty string if a parameter is not defined');
+ }
+
+ public function testGetInt()
+ {
+ $bag = new ParameterBag(array('digits' => '0123'));
+
+ $this->assertEquals(123, $bag->getInt('digits'), '->getInt() gets a value of parameter as integer');
+ $this->assertEquals(0, $bag->getInt('unknown'), '->getInt() returns zero if a parameter is not defined');
+ }
+
+ public function testFilter()
+ {
+ $bag = new ParameterBag(array(
+ 'digits' => '0123ab',
+ 'email' => 'example@example.com',
+ 'url' => 'http://example.com/foo',
+ 'dec' => '256',
+ 'hex' => '0x100',
+ 'array' => array('bang'),
+ ));
+
+ $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found');
+
+ $this->assertEquals('0123', $bag->filter('digits', '', FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters');
+
+ $this->assertEquals('example@example.com', $bag->filter('email', '', FILTER_VALIDATE_EMAIL), '->filter() gets a value of parameter as email');
+
+ $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, array('flags' => FILTER_FLAG_PATH_REQUIRED)), '->filter() gets a value of parameter as URL with a path');
+
+ // This test is repeated for code-coverage
+ $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as URL with a path');
+
+ $this->assertFalse($bag->filter('dec', '', FILTER_VALIDATE_INT, array(
+ 'flags' => FILTER_FLAG_ALLOW_HEX,
+ 'options' => array('min_range' => 1, 'max_range' => 0xff),
+ )), '->filter() gets a value of parameter as integer between boundaries');
+
+ $this->assertFalse($bag->filter('hex', '', FILTER_VALIDATE_INT, array(
+ 'flags' => FILTER_FLAG_ALLOW_HEX,
+ 'options' => array('min_range' => 1, 'max_range' => 0xff),
+ )), '->filter() gets a value of parameter as integer between boundaries');
+
+ $this->assertEquals(array('bang'), $bag->filter('array', ''), '->filter() gets a value of parameter as an array');
+ }
+
+ public function testGetIterator()
+ {
+ $parameters = array('foo' => 'bar', 'hello' => 'world');
+ $bag = new ParameterBag($parameters);
+
+ $i = 0;
+ foreach ($bag as $key => $val) {
+ ++$i;
+ $this->assertEquals($parameters[$key], $val);
+ }
+
+ $this->assertEquals(count($parameters), $i);
+ }
+
+ public function testCount()
+ {
+ $parameters = array('foo' => 'bar', 'hello' => 'world');
+ $bag = new ParameterBag($parameters);
+
+ $this->assertCount(count($parameters), $bag);
+ }
+
+ public function testGetBoolean()
+ {
+ $parameters = array('string_true' => 'true', 'string_false' => 'false');
+ $bag = new ParameterBag($parameters);
+
+ $this->assertTrue($bag->getBoolean('string_true'), '->getBoolean() gets the string true as boolean true');
+ $this->assertFalse($bag->getBoolean('string_false'), '->getBoolean() gets the string false as boolean false');
+ $this->assertFalse($bag->getBoolean('unknown'), '->getBoolean() returns false if a parameter is not defined');
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php
new file mode 100644
index 0000000..d389e83
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+class RedirectResponseTest extends TestCase
+{
+ public function testGenerateMetaRedirect()
+ {
+ $response = new RedirectResponse('foo.bar');
+
+ $this->assertEquals(1, preg_match(
+ '##',
+ preg_replace(array('/\s+/', '/\'/'), array(' ', '"'), $response->getContent())
+ ));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRedirectResponseConstructorNullUrl()
+ {
+ $response = new RedirectResponse(null);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRedirectResponseConstructorWrongStatusCode()
+ {
+ $response = new RedirectResponse('foo.bar', 404);
+ }
+
+ public function testGenerateLocationHeader()
+ {
+ $response = new RedirectResponse('foo.bar');
+
+ $this->assertTrue($response->headers->has('Location'));
+ $this->assertEquals('foo.bar', $response->headers->get('Location'));
+ }
+
+ public function testGetTargetUrl()
+ {
+ $response = new RedirectResponse('foo.bar');
+
+ $this->assertEquals('foo.bar', $response->getTargetUrl());
+ }
+
+ public function testSetTargetUrl()
+ {
+ $response = new RedirectResponse('foo.bar');
+ $response->setTargetUrl('baz.beep');
+
+ $this->assertEquals('baz.beep', $response->getTargetUrl());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetTargetUrlNull()
+ {
+ $response = new RedirectResponse('foo.bar');
+ $response->setTargetUrl(null);
+ }
+
+ public function testCreate()
+ {
+ $response = RedirectResponse::create('foo', 301);
+
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
+ $this->assertEquals(301, $response->getStatusCode());
+ }
+
+ public function testCacheHeaders()
+ {
+ $response = new RedirectResponse('foo.bar', 301);
+ $this->assertFalse($response->headers->hasCacheControlDirective('no-cache'));
+
+ $response = new RedirectResponse('foo.bar', 301, array('cache-control' => 'max-age=86400'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('no-cache'));
+ $this->assertTrue($response->headers->hasCacheControlDirective('max-age'));
+
+ $response = new RedirectResponse('foo.bar', 302);
+ $this->assertTrue($response->headers->hasCacheControlDirective('no-cache'));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php b/assets/php/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php
new file mode 100644
index 0000000..b5d8004
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php
@@ -0,0 +1,151 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\RequestMatcher;
+use Symfony\Component\HttpFoundation\Request;
+
+class RequestMatcherTest extends TestCase
+{
+ /**
+ * @dataProvider getMethodData
+ */
+ public function testMethod($requestMethod, $matcherMethod, $isMatch)
+ {
+ $matcher = new RequestMatcher();
+ $matcher->matchMethod($matcherMethod);
+ $request = Request::create('', $requestMethod);
+ $this->assertSame($isMatch, $matcher->matches($request));
+
+ $matcher = new RequestMatcher(null, null, $matcherMethod);
+ $request = Request::create('', $requestMethod);
+ $this->assertSame($isMatch, $matcher->matches($request));
+ }
+
+ public function getMethodData()
+ {
+ return array(
+ array('get', 'get', true),
+ array('get', array('get', 'post'), true),
+ array('get', 'post', false),
+ array('get', 'GET', true),
+ array('get', array('GET', 'POST'), true),
+ array('get', 'POST', false),
+ );
+ }
+
+ public function testScheme()
+ {
+ $httpRequest = $request = $request = Request::create('');
+ $httpsRequest = $request = $request = Request::create('', 'get', array(), array(), array(), array('HTTPS' => 'on'));
+
+ $matcher = new RequestMatcher();
+ $matcher->matchScheme('https');
+ $this->assertFalse($matcher->matches($httpRequest));
+ $this->assertTrue($matcher->matches($httpsRequest));
+
+ $matcher->matchScheme('http');
+ $this->assertFalse($matcher->matches($httpsRequest));
+ $this->assertTrue($matcher->matches($httpRequest));
+
+ $matcher = new RequestMatcher();
+ $this->assertTrue($matcher->matches($httpsRequest));
+ $this->assertTrue($matcher->matches($httpRequest));
+ }
+
+ /**
+ * @dataProvider getHostData
+ */
+ public function testHost($pattern, $isMatch)
+ {
+ $matcher = new RequestMatcher();
+ $request = Request::create('', 'get', array(), array(), array(), array('HTTP_HOST' => 'foo.example.com'));
+
+ $matcher->matchHost($pattern);
+ $this->assertSame($isMatch, $matcher->matches($request));
+
+ $matcher = new RequestMatcher(null, $pattern);
+ $this->assertSame($isMatch, $matcher->matches($request));
+ }
+
+ public function getHostData()
+ {
+ return array(
+ array('.*\.example\.com', true),
+ array('\.example\.com$', true),
+ array('^.*\.example\.com$', true),
+ array('.*\.sensio\.com', false),
+ array('.*\.example\.COM', true),
+ array('\.example\.COM$', true),
+ array('^.*\.example\.COM$', true),
+ array('.*\.sensio\.COM', false),
+ );
+ }
+
+ public function testPath()
+ {
+ $matcher = new RequestMatcher();
+
+ $request = Request::create('/admin/foo');
+
+ $matcher->matchPath('/admin/.*');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchPath('/admin');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchPath('^/admin/.*$');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchMethod('/blog/.*');
+ $this->assertFalse($matcher->matches($request));
+ }
+
+ public function testPathWithLocaleIsNotSupported()
+ {
+ $matcher = new RequestMatcher();
+ $request = Request::create('/en/login');
+ $request->setLocale('en');
+
+ $matcher->matchPath('^/{_locale}/login$');
+ $this->assertFalse($matcher->matches($request));
+ }
+
+ public function testPathWithEncodedCharacters()
+ {
+ $matcher = new RequestMatcher();
+ $request = Request::create('/admin/fo%20o');
+ $matcher->matchPath('^/admin/fo o*$');
+ $this->assertTrue($matcher->matches($request));
+ }
+
+ public function testAttributes()
+ {
+ $matcher = new RequestMatcher();
+
+ $request = Request::create('/admin/foo');
+ $request->attributes->set('foo', 'foo_bar');
+
+ $matcher->matchAttribute('foo', 'foo_.*');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchAttribute('foo', 'foo');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchAttribute('foo', '^foo_bar$');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchAttribute('foo', 'babar');
+ $this->assertFalse($matcher->matches($request));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/RequestStackTest.php b/assets/php/vendor/symfony/http-foundation/Tests/RequestStackTest.php
new file mode 100644
index 0000000..a84fb26
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/RequestStackTest.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+class RequestStackTest extends TestCase
+{
+ public function testGetCurrentRequest()
+ {
+ $requestStack = new RequestStack();
+ $this->assertNull($requestStack->getCurrentRequest());
+
+ $request = Request::create('/foo');
+
+ $requestStack->push($request);
+ $this->assertSame($request, $requestStack->getCurrentRequest());
+
+ $this->assertSame($request, $requestStack->pop());
+ $this->assertNull($requestStack->getCurrentRequest());
+
+ $this->assertNull($requestStack->pop());
+ }
+
+ public function testGetMasterRequest()
+ {
+ $requestStack = new RequestStack();
+ $this->assertNull($requestStack->getMasterRequest());
+
+ $masterRequest = Request::create('/foo');
+ $subRequest = Request::create('/bar');
+
+ $requestStack->push($masterRequest);
+ $requestStack->push($subRequest);
+
+ $this->assertSame($masterRequest, $requestStack->getMasterRequest());
+ }
+
+ public function testGetParentRequest()
+ {
+ $requestStack = new RequestStack();
+ $this->assertNull($requestStack->getParentRequest());
+
+ $masterRequest = Request::create('/foo');
+
+ $requestStack->push($masterRequest);
+ $this->assertNull($requestStack->getParentRequest());
+
+ $firstSubRequest = Request::create('/bar');
+
+ $requestStack->push($firstSubRequest);
+ $this->assertSame($masterRequest, $requestStack->getParentRequest());
+
+ $secondSubRequest = Request::create('/baz');
+
+ $requestStack->push($secondSubRequest);
+ $this->assertSame($firstSubRequest, $requestStack->getParentRequest());
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/RequestTest.php b/assets/php/vendor/symfony/http-foundation/Tests/RequestTest.php
new file mode 100644
index 0000000..230ad15
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/RequestTest.php
@@ -0,0 +1,2329 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Request;
+
+class RequestTest extends TestCase
+{
+ protected function tearDown()
+ {
+ // reset
+ Request::setTrustedProxies(array(), -1);
+ }
+
+ public function testInitialize()
+ {
+ $request = new Request();
+
+ $request->initialize(array('foo' => 'bar'));
+ $this->assertEquals('bar', $request->query->get('foo'), '->initialize() takes an array of query parameters as its first argument');
+
+ $request->initialize(array(), array('foo' => 'bar'));
+ $this->assertEquals('bar', $request->request->get('foo'), '->initialize() takes an array of request parameters as its second argument');
+
+ $request->initialize(array(), array(), array('foo' => 'bar'));
+ $this->assertEquals('bar', $request->attributes->get('foo'), '->initialize() takes an array of attributes as its third argument');
+
+ $request->initialize(array(), array(), array(), array(), array(), array('HTTP_FOO' => 'bar'));
+ $this->assertEquals('bar', $request->headers->get('FOO'), '->initialize() takes an array of HTTP headers as its sixth argument');
+ }
+
+ public function testGetLocale()
+ {
+ $request = new Request();
+ $request->setLocale('pl');
+ $locale = $request->getLocale();
+ $this->assertEquals('pl', $locale);
+ }
+
+ public function testGetUser()
+ {
+ $request = Request::create('http://user:password@test.com');
+ $user = $request->getUser();
+
+ $this->assertEquals('user', $user);
+ }
+
+ public function testGetPassword()
+ {
+ $request = Request::create('http://user:password@test.com');
+ $password = $request->getPassword();
+
+ $this->assertEquals('password', $password);
+ }
+
+ public function testIsNoCache()
+ {
+ $request = new Request();
+ $isNoCache = $request->isNoCache();
+
+ $this->assertFalse($isNoCache);
+ }
+
+ public function testGetContentType()
+ {
+ $request = new Request();
+ $contentType = $request->getContentType();
+
+ $this->assertNull($contentType);
+ }
+
+ public function testSetDefaultLocale()
+ {
+ $request = new Request();
+ $request->setDefaultLocale('pl');
+ $locale = $request->getLocale();
+
+ $this->assertEquals('pl', $locale);
+ }
+
+ public function testCreate()
+ {
+ $request = Request::create('http://test.com/foo?bar=baz');
+ $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com/foo', 'GET', array('bar' => 'baz'));
+ $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com/foo?bar=foo', 'GET', array('bar' => 'baz'));
+ $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('https://test.com/foo?bar=baz');
+ $this->assertEquals('https://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertTrue($request->isSecure());
+
+ $request = Request::create('test.com:90/foo');
+ $this->assertEquals('http://test.com:90/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('test.com', $request->getHost());
+ $this->assertEquals('test.com:90', $request->getHttpHost());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('https://test.com:90/foo');
+ $this->assertEquals('https://test.com:90/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('test.com', $request->getHost());
+ $this->assertEquals('test.com:90', $request->getHttpHost());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ $request = Request::create('https://127.0.0.1:90/foo');
+ $this->assertEquals('https://127.0.0.1:90/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('127.0.0.1', $request->getHost());
+ $this->assertEquals('127.0.0.1:90', $request->getHttpHost());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ $request = Request::create('https://[::1]:90/foo');
+ $this->assertEquals('https://[::1]:90/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('[::1]', $request->getHost());
+ $this->assertEquals('[::1]:90', $request->getHttpHost());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ $request = Request::create('https://[::1]/foo');
+ $this->assertEquals('https://[::1]/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('[::1]', $request->getHost());
+ $this->assertEquals('[::1]', $request->getHttpHost());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}';
+ $request = Request::create('http://example.com/jsonrpc', 'POST', array(), array(), array(), array(), $json);
+ $this->assertEquals($json, $request->getContent());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com');
+ $this->assertEquals('http://test.com/', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com?test=1');
+ $this->assertEquals('http://test.com/?test=1', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('test=1', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com:90/?test=1');
+ $this->assertEquals('http://test.com:90/?test=1', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('test=1', $request->getQueryString());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertEquals('test.com:90', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://username:password@test.com');
+ $this->assertEquals('http://test.com/', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertEquals('username', $request->getUser());
+ $this->assertEquals('password', $request->getPassword());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://username@test.com');
+ $this->assertEquals('http://test.com/', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertEquals('username', $request->getUser());
+ $this->assertSame('', $request->getPassword());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com/?foo');
+ $this->assertEquals('/?foo', $request->getRequestUri());
+ $this->assertEquals(array('foo' => ''), $request->query->all());
+
+ // assume rewrite rule: (.*) --> app/app.php; app/ is a symlink to a symfony web/ directory
+ $request = Request::create('http://test.com/apparthotel-1234', 'GET', array(), array(), array(),
+ array(
+ 'DOCUMENT_ROOT' => '/var/www/www.test.com',
+ 'SCRIPT_FILENAME' => '/var/www/www.test.com/app/app.php',
+ 'SCRIPT_NAME' => '/app/app.php',
+ 'PHP_SELF' => '/app/app.php/apparthotel-1234',
+ ));
+ $this->assertEquals('http://test.com/apparthotel-1234', $request->getUri());
+ $this->assertEquals('/apparthotel-1234', $request->getPathInfo());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+ }
+
+ public function testCreateCheckPrecedence()
+ {
+ // server is used by default
+ $request = Request::create('/', 'DELETE', array(), array(), array(), array(
+ 'HTTP_HOST' => 'example.com',
+ 'HTTPS' => 'on',
+ 'SERVER_PORT' => 443,
+ 'PHP_AUTH_USER' => 'fabien',
+ 'PHP_AUTH_PW' => 'pa$$',
+ 'QUERY_STRING' => 'foo=bar',
+ 'CONTENT_TYPE' => 'application/json',
+ ));
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertTrue($request->isSecure());
+ $this->assertEquals('fabien', $request->getUser());
+ $this->assertEquals('pa$$', $request->getPassword());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals('application/json', $request->headers->get('CONTENT_TYPE'));
+
+ // URI has precedence over server
+ $request = Request::create('http://thomas:pokemon@example.net:8080/?foo=bar', 'GET', array(), array(), array(), array(
+ 'HTTP_HOST' => 'example.com',
+ 'HTTPS' => 'on',
+ 'SERVER_PORT' => 443,
+ ));
+ $this->assertEquals('example.net', $request->getHost());
+ $this->assertEquals(8080, $request->getPort());
+ $this->assertFalse($request->isSecure());
+ $this->assertEquals('thomas', $request->getUser());
+ $this->assertEquals('pokemon', $request->getPassword());
+ $this->assertEquals('foo=bar', $request->getQueryString());
+ }
+
+ public function testDuplicate()
+ {
+ $request = new Request(array('foo' => 'bar'), array('foo' => 'bar'), array('foo' => 'bar'), array(), array(), array('HTTP_FOO' => 'bar'));
+ $dup = $request->duplicate();
+
+ $this->assertEquals($request->query->all(), $dup->query->all(), '->duplicate() duplicates a request an copy the current query parameters');
+ $this->assertEquals($request->request->all(), $dup->request->all(), '->duplicate() duplicates a request an copy the current request parameters');
+ $this->assertEquals($request->attributes->all(), $dup->attributes->all(), '->duplicate() duplicates a request an copy the current attributes');
+ $this->assertEquals($request->headers->all(), $dup->headers->all(), '->duplicate() duplicates a request an copy the current HTTP headers');
+
+ $dup = $request->duplicate(array('foo' => 'foobar'), array('foo' => 'foobar'), array('foo' => 'foobar'), array(), array(), array('HTTP_FOO' => 'foobar'));
+
+ $this->assertEquals(array('foo' => 'foobar'), $dup->query->all(), '->duplicate() overrides the query parameters if provided');
+ $this->assertEquals(array('foo' => 'foobar'), $dup->request->all(), '->duplicate() overrides the request parameters if provided');
+ $this->assertEquals(array('foo' => 'foobar'), $dup->attributes->all(), '->duplicate() overrides the attributes if provided');
+ $this->assertEquals(array('foo' => array('foobar')), $dup->headers->all(), '->duplicate() overrides the HTTP header if provided');
+ }
+
+ public function testDuplicateWithFormat()
+ {
+ $request = new Request(array(), array(), array('_format' => 'json'));
+ $dup = $request->duplicate();
+
+ $this->assertEquals('json', $dup->getRequestFormat());
+ $this->assertEquals('json', $dup->attributes->get('_format'));
+
+ $request = new Request();
+ $request->setRequestFormat('xml');
+ $dup = $request->duplicate();
+
+ $this->assertEquals('xml', $dup->getRequestFormat());
+ }
+
+ /**
+ * @dataProvider getFormatToMimeTypeMapProviderWithAdditionalNullFormat
+ */
+ public function testGetFormatFromMimeType($format, $mimeTypes)
+ {
+ $request = new Request();
+ foreach ($mimeTypes as $mime) {
+ $this->assertEquals($format, $request->getFormat($mime));
+ }
+ $request->setFormat($format, $mimeTypes);
+ foreach ($mimeTypes as $mime) {
+ $this->assertEquals($format, $request->getFormat($mime));
+
+ if (null !== $format) {
+ $this->assertEquals($mimeTypes[0], $request->getMimeType($format));
+ }
+ }
+ }
+
+ public function getFormatToMimeTypeMapProviderWithAdditionalNullFormat()
+ {
+ return array_merge(
+ array(array(null, array(null, 'unexistent-mime-type'))),
+ $this->getFormatToMimeTypeMapProvider()
+ );
+ }
+
+ public function testGetFormatFromMimeTypeWithParameters()
+ {
+ $request = new Request();
+ $this->assertEquals('json', $request->getFormat('application/json; charset=utf-8'));
+ }
+
+ /**
+ * @dataProvider getFormatToMimeTypeMapProvider
+ */
+ public function testGetMimeTypeFromFormat($format, $mimeTypes)
+ {
+ $request = new Request();
+ $this->assertEquals($mimeTypes[0], $request->getMimeType($format));
+ }
+
+ /**
+ * @dataProvider getFormatToMimeTypeMapProvider
+ */
+ public function testGetMimeTypesFromFormat($format, $mimeTypes)
+ {
+ $this->assertEquals($mimeTypes, Request::getMimeTypes($format));
+ }
+
+ public function testGetMimeTypesFromInexistentFormat()
+ {
+ $request = new Request();
+ $this->assertNull($request->getMimeType('foo'));
+ $this->assertEquals(array(), Request::getMimeTypes('foo'));
+ }
+
+ public function testGetFormatWithCustomMimeType()
+ {
+ $request = new Request();
+ $request->setFormat('custom', 'application/vnd.foo.api;myversion=2.3');
+ $this->assertEquals('custom', $request->getFormat('application/vnd.foo.api;myversion=2.3'));
+ }
+
+ public function getFormatToMimeTypeMapProvider()
+ {
+ return array(
+ array('txt', array('text/plain')),
+ array('js', array('application/javascript', 'application/x-javascript', 'text/javascript')),
+ array('css', array('text/css')),
+ array('json', array('application/json', 'application/x-json')),
+ array('jsonld', array('application/ld+json')),
+ array('xml', array('text/xml', 'application/xml', 'application/x-xml')),
+ array('rdf', array('application/rdf+xml')),
+ array('atom', array('application/atom+xml')),
+ );
+ }
+
+ public function testGetUri()
+ {
+ $server = array();
+
+ // Standard Request on non default PORT
+ // http://host:8080/index.php/path/info?query=string
+
+ $server['HTTP_HOST'] = 'host:8080';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '8080';
+
+ $server['QUERY_STRING'] = 'query=string';
+ $server['REQUEST_URI'] = '/index.php/path/info?query=string';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['PATH_INFO'] = '/path/info';
+ $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info';
+ $server['PHP_SELF'] = '/index_dev.php/path/info';
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+ $request = new Request();
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://host:8080/index.php/path/info?query=string', $request->getUri(), '->getUri() with non default port');
+
+ // Use std port number
+ $server['HTTP_HOST'] = 'host';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://host/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port');
+
+ // Without HOST HEADER
+ unset($server['HTTP_HOST']);
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://servername/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port without HOST_HEADER');
+
+ // Request with URL REWRITING (hide index.php)
+ // RewriteCond %{REQUEST_FILENAME} !-f
+ // RewriteRule ^(.*)$ index.php [QSA,L]
+ // http://host:8080/path/info?query=string
+ $server = array();
+ $server['HTTP_HOST'] = 'host:8080';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '8080';
+
+ $server['REDIRECT_QUERY_STRING'] = 'query=string';
+ $server['REDIRECT_URL'] = '/path/info';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['QUERY_STRING'] = 'query=string';
+ $server['REQUEST_URI'] = '/path/info?toto=test&1=1';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['PHP_SELF'] = '/index.php';
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://host:8080/path/info?query=string', $request->getUri(), '->getUri() with rewrite');
+
+ // Use std port number
+ // http://host/path/info?query=string
+ $server['HTTP_HOST'] = 'host';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://host/path/info?query=string', $request->getUri(), '->getUri() with rewrite and default port');
+
+ // Without HOST HEADER
+ unset($server['HTTP_HOST']);
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://servername/path/info?query=string', $request->getUri(), '->getUri() with rewrite, default port without HOST_HEADER');
+
+ // With encoded characters
+
+ $server = array(
+ 'HTTP_HOST' => 'host:8080',
+ 'SERVER_NAME' => 'servername',
+ 'SERVER_PORT' => '8080',
+ 'QUERY_STRING' => 'query=string',
+ 'REQUEST_URI' => '/ba%20se/index_dev.php/foo%20bar/in+fo?query=string',
+ 'SCRIPT_NAME' => '/ba se/index_dev.php',
+ 'PATH_TRANSLATED' => 'redirect:/index.php/foo bar/in+fo',
+ 'PHP_SELF' => '/ba se/index_dev.php/path/info',
+ 'SCRIPT_FILENAME' => '/some/where/ba se/index_dev.php',
+ );
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals(
+ 'http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string',
+ $request->getUri()
+ );
+
+ // with user info
+
+ $server['PHP_AUTH_USER'] = 'fabien';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri());
+
+ $server['PHP_AUTH_PW'] = 'symfony';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri());
+ }
+
+ public function testGetUriForPath()
+ {
+ $request = Request::create('http://test.com/foo?bar=baz');
+ $this->assertEquals('http://test.com/some/path', $request->getUriForPath('/some/path'));
+
+ $request = Request::create('http://test.com:90/foo?bar=baz');
+ $this->assertEquals('http://test.com:90/some/path', $request->getUriForPath('/some/path'));
+
+ $request = Request::create('https://test.com/foo?bar=baz');
+ $this->assertEquals('https://test.com/some/path', $request->getUriForPath('/some/path'));
+
+ $request = Request::create('https://test.com:90/foo?bar=baz');
+ $this->assertEquals('https://test.com:90/some/path', $request->getUriForPath('/some/path'));
+
+ $server = array();
+
+ // Standard Request on non default PORT
+ // http://host:8080/index.php/path/info?query=string
+
+ $server['HTTP_HOST'] = 'host:8080';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '8080';
+
+ $server['QUERY_STRING'] = 'query=string';
+ $server['REQUEST_URI'] = '/index.php/path/info?query=string';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['PATH_INFO'] = '/path/info';
+ $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info';
+ $server['PHP_SELF'] = '/index_dev.php/path/info';
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+ $request = new Request();
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://host:8080/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with non default port');
+
+ // Use std port number
+ $server['HTTP_HOST'] = 'host';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://host/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port');
+
+ // Without HOST HEADER
+ unset($server['HTTP_HOST']);
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://servername/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port without HOST_HEADER');
+
+ // Request with URL REWRITING (hide index.php)
+ // RewriteCond %{REQUEST_FILENAME} !-f
+ // RewriteRule ^(.*)$ index.php [QSA,L]
+ // http://host:8080/path/info?query=string
+ $server = array();
+ $server['HTTP_HOST'] = 'host:8080';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '8080';
+
+ $server['REDIRECT_QUERY_STRING'] = 'query=string';
+ $server['REDIRECT_URL'] = '/path/info';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['QUERY_STRING'] = 'query=string';
+ $server['REQUEST_URI'] = '/path/info?toto=test&1=1';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['PHP_SELF'] = '/index.php';
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://host:8080/some/path', $request->getUriForPath('/some/path'), '->getUri() with rewrite');
+
+ // Use std port number
+ // http://host/path/info?query=string
+ $server['HTTP_HOST'] = 'host';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://host/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite and default port');
+
+ // Without HOST HEADER
+ unset($server['HTTP_HOST']);
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite, default port without HOST_HEADER');
+ $this->assertEquals('servername', $request->getHttpHost());
+
+ // with user info
+
+ $server['PHP_AUTH_USER'] = 'fabien';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'));
+
+ $server['PHP_AUTH_PW'] = 'symfony';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'));
+ }
+
+ /**
+ * @dataProvider getRelativeUriForPathData()
+ */
+ public function testGetRelativeUriForPath($expected, $pathinfo, $path)
+ {
+ $this->assertEquals($expected, Request::create($pathinfo)->getRelativeUriForPath($path));
+ }
+
+ public function getRelativeUriForPathData()
+ {
+ return array(
+ array('me.png', '/foo', '/me.png'),
+ array('../me.png', '/foo/bar', '/me.png'),
+ array('me.png', '/foo/bar', '/foo/me.png'),
+ array('../baz/me.png', '/foo/bar/b', '/foo/baz/me.png'),
+ array('../../fooz/baz/me.png', '/foo/bar/b', '/fooz/baz/me.png'),
+ array('baz/me.png', '/foo/bar/b', 'baz/me.png'),
+ );
+ }
+
+ public function testGetUserInfo()
+ {
+ $request = new Request();
+
+ $server = array('PHP_AUTH_USER' => 'fabien');
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('fabien', $request->getUserInfo());
+
+ $server['PHP_AUTH_USER'] = '0';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('0', $request->getUserInfo());
+
+ $server['PHP_AUTH_PW'] = '0';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('0:0', $request->getUserInfo());
+ }
+
+ public function testGetSchemeAndHttpHost()
+ {
+ $request = new Request();
+
+ $server = array();
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '90';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+ $server['PHP_AUTH_USER'] = 'fabien';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+ $server['PHP_AUTH_USER'] = '0';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+ $server['PHP_AUTH_PW'] = '0';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+ }
+
+ /**
+ * @dataProvider getQueryStringNormalizationData
+ */
+ public function testGetQueryString($query, $expectedQuery, $msg)
+ {
+ $request = new Request();
+
+ $request->server->set('QUERY_STRING', $query);
+ $this->assertSame($expectedQuery, $request->getQueryString(), $msg);
+ }
+
+ public function getQueryStringNormalizationData()
+ {
+ return array(
+ array('foo', 'foo', 'works with valueless parameters'),
+ array('foo=', 'foo=', 'includes a dangling equal sign'),
+ array('bar=&foo=bar', 'bar=&foo=bar', '->works with empty parameters'),
+ array('foo=bar&bar=', 'bar=&foo=bar', 'sorts keys alphabetically'),
+
+ // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
+ // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str.
+ array('him=John%20Doe&her=Jane+Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes spaces in both encodings "%20" and "+"'),
+
+ array('foo[]=1&foo[]=2', 'foo%5B%5D=1&foo%5B%5D=2', 'allows array notation'),
+ array('foo=1&foo=2', 'foo=1&foo=2', 'allows repeated parameters'),
+ array('pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'),
+ array('0', '0', 'allows "0"'),
+ array('Jane Doe&John%20Doe', 'Jane%20Doe&John%20Doe', 'normalizes encoding in keys'),
+ array('her=Jane Doe&him=John%20Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes encoding in values'),
+ array('foo=bar&&&test&&', 'foo=bar&test', 'removes unneeded delimiters'),
+ array('formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'),
+
+ // Ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
+ // PHP also does not include them when building _GET.
+ array('foo=bar&=a=b&=x=y', 'foo=bar', 'removes params with empty key'),
+ );
+ }
+
+ public function testGetQueryStringReturnsNull()
+ {
+ $request = new Request();
+
+ $this->assertNull($request->getQueryString(), '->getQueryString() returns null for non-existent query string');
+
+ $request->server->set('QUERY_STRING', '');
+ $this->assertNull($request->getQueryString(), '->getQueryString() returns null for empty query string');
+ }
+
+ public function testGetHost()
+ {
+ $request = new Request();
+
+ $request->initialize(array('foo' => 'bar'));
+ $this->assertEquals('', $request->getHost(), '->getHost() return empty string if not initialized');
+
+ $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.example.com'));
+ $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header');
+
+ // Host header with port number
+ $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.example.com:8080'));
+ $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header with port number');
+
+ // Server values
+ $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.example.com'));
+ $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from server name');
+
+ $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.example.com', 'HTTP_HOST' => 'www.host.com'));
+ $this->assertEquals('www.host.com', $request->getHost(), '->getHost() value from Host header has priority over SERVER_NAME ');
+ }
+
+ public function testGetPort()
+ {
+ $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+ 'HTTP_X_FORWARDED_PROTO' => 'https',
+ 'HTTP_X_FORWARDED_PORT' => '443',
+ ));
+ $port = $request->getPort();
+
+ $this->assertEquals(80, $port, 'Without trusted proxies FORWARDED_PROTO and FORWARDED_PORT are ignored.');
+
+ Request::setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_ALL);
+ $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+ 'HTTP_X_FORWARDED_PROTO' => 'https',
+ 'HTTP_X_FORWARDED_PORT' => '8443',
+ ));
+ $this->assertEquals(80, $request->getPort(), 'With PROTO and PORT on untrusted connection server value takes precedence.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(8443, $request->getPort(), 'With PROTO and PORT set PORT takes precedence.');
+
+ $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+ 'HTTP_X_FORWARDED_PROTO' => 'https',
+ ));
+ $this->assertEquals(80, $request->getPort(), 'With only PROTO set getPort() ignores trusted headers on untrusted connection.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(443, $request->getPort(), 'With only PROTO set getPort() defaults to 443.');
+
+ $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+ 'HTTP_X_FORWARDED_PROTO' => 'http',
+ ));
+ $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() ignores trusted headers on untrusted connection.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() returns port of the original request.');
+
+ $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+ 'HTTP_X_FORWARDED_PROTO' => 'On',
+ ));
+ $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is On, getPort() ignores trusted headers on untrusted connection.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is On, getPort() defaults to 443.');
+
+ $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+ 'HTTP_X_FORWARDED_PROTO' => '1',
+ ));
+ $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is 1, getPort() ignores trusted headers on untrusted connection.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is 1, getPort() defaults to 443.');
+
+ $request = Request::create('http://example.com', 'GET', array(), array(), array(), array(
+ 'HTTP_X_FORWARDED_PROTO' => 'something-else',
+ ));
+ $port = $request->getPort();
+ $this->assertEquals(80, $port, 'With only PROTO set and value is not recognized, getPort() defaults to 80.');
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testGetHostWithFakeHttpHostValue()
+ {
+ $request = new Request();
+ $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.host.com?query=string'));
+ $request->getHost();
+ }
+
+ public function testGetSetMethod()
+ {
+ $request = new Request();
+
+ $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns GET if no method is defined');
+
+ $request->setMethod('get');
+ $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns an uppercased string');
+
+ $request->setMethod('PURGE');
+ $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method even if it is not a standard one');
+
+ $request->setMethod('POST');
+ $this->assertEquals('POST', $request->getMethod(), '->getMethod() returns the method POST if no _method is defined');
+
+ $request->setMethod('POST');
+ $request->request->set('_method', 'purge');
+ $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled');
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->request->set('_method', 'purge');
+
+ $this->assertFalse(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be disabled by default');
+
+ Request::enableHttpMethodParameterOverride();
+
+ $this->assertTrue(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be enabled now but it is not');
+
+ $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST');
+ $this->disableHttpMethodParameterOverride();
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->query->set('_method', 'purge');
+ $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled');
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->query->set('_method', 'purge');
+ Request::enableHttpMethodParameterOverride();
+ $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST');
+ $this->disableHttpMethodParameterOverride();
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete');
+ $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override even though _method is set if defined and POST');
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete');
+ $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override if defined and POST');
+ }
+
+ /**
+ * @dataProvider getClientIpsProvider
+ */
+ public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies)
+ {
+ $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies);
+
+ $this->assertEquals($expected[0], $request->getClientIp());
+ }
+
+ /**
+ * @dataProvider getClientIpsProvider
+ */
+ public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $trustedProxies)
+ {
+ $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies);
+
+ $this->assertEquals($expected, $request->getClientIps());
+ }
+
+ /**
+ * @dataProvider getClientIpsForwardedProvider
+ */
+ public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies)
+ {
+ $request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies);
+
+ $this->assertEquals($expected, $request->getClientIps());
+ }
+
+ public function getClientIpsForwardedProvider()
+ {
+ // $expected $remoteAddr $httpForwarded $trustedProxies
+ return array(
+ array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', null),
+ array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', array('127.0.0.1')),
+ array(array('88.88.88.88'), '127.0.0.1', 'for="88.88.88.88:80"', array('127.0.0.1')),
+ array(array('192.0.2.60'), '::1', 'for=192.0.2.60;proto=http;by=203.0.113.43', array('::1')),
+ array(array('2620:0:1cfe:face:b00c::3', '192.0.2.43'), '::1', 'for=192.0.2.43, for=2620:0:1cfe:face:b00c::3', array('::1')),
+ array(array('2001:db8:cafe::17'), '::1', 'for="[2001:db8:cafe::17]:4711', array('::1')),
+ );
+ }
+
+ public function getClientIpsProvider()
+ {
+ // $expected $remoteAddr $httpForwardedFor $trustedProxies
+ return array(
+ // simple IPv4
+ array(array('88.88.88.88'), '88.88.88.88', null, null),
+ // trust the IPv4 remote addr
+ array(array('88.88.88.88'), '88.88.88.88', null, array('88.88.88.88')),
+
+ // simple IPv6
+ array(array('::1'), '::1', null, null),
+ // trust the IPv6 remote addr
+ array(array('::1'), '::1', null, array('::1')),
+
+ // forwarded for with remote IPv4 addr not trusted
+ array(array('127.0.0.1'), '127.0.0.1', '88.88.88.88', null),
+ // forwarded for with remote IPv4 addr trusted
+ array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1')),
+ // forwarded for with remote IPv4 and all FF addrs trusted
+ array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1', '88.88.88.88')),
+ // forwarded for with remote IPv4 range trusted
+ array(array('88.88.88.88'), '123.45.67.89', '88.88.88.88', array('123.45.67.0/24')),
+
+ // forwarded for with remote IPv6 addr not trusted
+ array(array('1620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', null),
+ // forwarded for with remote IPv6 addr trusted
+ array(array('2620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')),
+ // forwarded for with remote IPv6 range trusted
+ array(array('88.88.88.88'), '2a01:198:603:0:396e:4789:8e99:890f', '88.88.88.88', array('2a01:198:603:0::/65')),
+
+ // multiple forwarded for with remote IPv4 addr trusted
+ array(array('88.88.88.88', '87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89')),
+ // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted
+ array(array('87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '88.88.88.88')),
+ // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle
+ array(array('88.88.88.88', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21')),
+ // multiple forwarded for with remote IPv4 addr and all reverse proxies trusted
+ array(array('127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21', '88.88.88.88', '127.0.0.1')),
+
+ // multiple forwarded for with remote IPv6 addr trusted
+ array(array('2620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')),
+ // multiple forwarded for with remote IPv6 addr and some reverse proxies trusted
+ array(array('3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3')),
+ // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle
+ array(array('2620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3,3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3')),
+
+ // client IP with port
+ array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88:12345, 127.0.0.1', array('127.0.0.1')),
+
+ // invalid forwarded IP is ignored
+ array(array('88.88.88.88'), '127.0.0.1', 'unknown,88.88.88.88', array('127.0.0.1')),
+ array(array('88.88.88.88'), '127.0.0.1', '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2,88.88.88.88', array('127.0.0.1')),
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException
+ * @dataProvider getClientIpsWithConflictingHeadersProvider
+ */
+ public function testGetClientIpsWithConflictingHeaders($httpForwarded, $httpXForwardedFor)
+ {
+ $request = new Request();
+
+ $server = array(
+ 'REMOTE_ADDR' => '88.88.88.88',
+ 'HTTP_FORWARDED' => $httpForwarded,
+ 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+ );
+
+ Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL | Request::HEADER_FORWARDED);
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $request->getClientIps();
+ }
+
+ /**
+ * @dataProvider getClientIpsWithConflictingHeadersProvider
+ */
+ public function testGetClientIpsOnlyXHttpForwardedForTrusted($httpForwarded, $httpXForwardedFor)
+ {
+ $request = new Request();
+
+ $server = array(
+ 'REMOTE_ADDR' => '88.88.88.88',
+ 'HTTP_FORWARDED' => $httpForwarded,
+ 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+ );
+
+ Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_FOR);
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertSame(array_reverse(explode(',', $httpXForwardedFor)), $request->getClientIps());
+ }
+
+ public function getClientIpsWithConflictingHeadersProvider()
+ {
+ // $httpForwarded $httpXForwardedFor
+ return array(
+ array('for=87.65.43.21', '192.0.2.60'),
+ array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60'),
+ array('for=192.0.2.60', '192.0.2.60,87.65.43.21'),
+ array('for="::face", for=192.0.2.60', '192.0.2.60,192.0.2.43'),
+ array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60,87.65.43.21'),
+ );
+ }
+
+ /**
+ * @dataProvider getClientIpsWithAgreeingHeadersProvider
+ */
+ public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwardedFor, $expectedIps)
+ {
+ $request = new Request();
+
+ $server = array(
+ 'REMOTE_ADDR' => '88.88.88.88',
+ 'HTTP_FORWARDED' => $httpForwarded,
+ 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+ );
+
+ Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL);
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $clientIps = $request->getClientIps();
+
+ $this->assertSame($expectedIps, $clientIps);
+ }
+
+ public function getClientIpsWithAgreeingHeadersProvider()
+ {
+ // $httpForwarded $httpXForwardedFor
+ return array(
+ array('for="192.0.2.60"', '192.0.2.60', array('192.0.2.60')),
+ array('for=192.0.2.60, for=87.65.43.21', '192.0.2.60,87.65.43.21', array('87.65.43.21', '192.0.2.60')),
+ array('for="[::face]", for=192.0.2.60', '::face,192.0.2.60', array('192.0.2.60', '::face')),
+ array('for="192.0.2.60:80"', '192.0.2.60', array('192.0.2.60')),
+ array('for=192.0.2.60;proto=http;by=203.0.113.43', '192.0.2.60', array('192.0.2.60')),
+ array('for="[2001:db8:cafe::17]:4711"', '2001:db8:cafe::17', array('2001:db8:cafe::17')),
+ );
+ }
+
+ public function testGetContentWorksTwiceInDefaultMode()
+ {
+ $req = new Request();
+ $this->assertEquals('', $req->getContent());
+ $this->assertEquals('', $req->getContent());
+ }
+
+ public function testGetContentReturnsResource()
+ {
+ $req = new Request();
+ $retval = $req->getContent(true);
+ $this->assertInternalType('resource', $retval);
+ $this->assertEquals('', fread($retval, 1));
+ $this->assertTrue(feof($retval));
+ }
+
+ public function testGetContentReturnsResourceWhenContentSetInConstructor()
+ {
+ $req = new Request(array(), array(), array(), array(), array(), array(), 'MyContent');
+ $resource = $req->getContent(true);
+
+ $this->assertInternalType('resource', $resource);
+ $this->assertEquals('MyContent', stream_get_contents($resource));
+ }
+
+ public function testContentAsResource()
+ {
+ $resource = fopen('php://memory', 'r+');
+ fwrite($resource, 'My other content');
+ rewind($resource);
+
+ $req = new Request(array(), array(), array(), array(), array(), array(), $resource);
+ $this->assertEquals('My other content', stream_get_contents($req->getContent(true)));
+ $this->assertEquals('My other content', $req->getContent());
+ }
+
+ /**
+ * @expectedException \LogicException
+ * @dataProvider getContentCantBeCalledTwiceWithResourcesProvider
+ */
+ public function testGetContentCantBeCalledTwiceWithResources($first, $second)
+ {
+ if (\PHP_VERSION_ID >= 50600) {
+ $this->markTestSkipped('PHP >= 5.6 allows to open php://input several times.');
+ }
+
+ $req = new Request();
+ $req->getContent($first);
+ $req->getContent($second);
+ }
+
+ public function getContentCantBeCalledTwiceWithResourcesProvider()
+ {
+ return array(
+ 'Resource then fetch' => array(true, false),
+ 'Resource then resource' => array(true, true),
+ );
+ }
+
+ /**
+ * @dataProvider getContentCanBeCalledTwiceWithResourcesProvider
+ * @requires PHP 5.6
+ */
+ public function testGetContentCanBeCalledTwiceWithResources($first, $second)
+ {
+ $req = new Request();
+ $a = $req->getContent($first);
+ $b = $req->getContent($second);
+
+ if ($first) {
+ $a = stream_get_contents($a);
+ }
+
+ if ($second) {
+ $b = stream_get_contents($b);
+ }
+
+ $this->assertSame($a, $b);
+ }
+
+ public function getContentCanBeCalledTwiceWithResourcesProvider()
+ {
+ return array(
+ 'Fetch then fetch' => array(false, false),
+ 'Fetch then resource' => array(false, true),
+ 'Resource then fetch' => array(true, false),
+ 'Resource then resource' => array(true, true),
+ );
+ }
+
+ public function provideOverloadedMethods()
+ {
+ return array(
+ array('PUT'),
+ array('DELETE'),
+ array('PATCH'),
+ array('put'),
+ array('delete'),
+ array('patch'),
+ );
+ }
+
+ /**
+ * @dataProvider provideOverloadedMethods
+ */
+ public function testCreateFromGlobals($method)
+ {
+ $normalizedMethod = strtoupper($method);
+
+ $_GET['foo1'] = 'bar1';
+ $_POST['foo2'] = 'bar2';
+ $_COOKIE['foo3'] = 'bar3';
+ $_FILES['foo4'] = array('bar4');
+ $_SERVER['foo5'] = 'bar5';
+
+ $request = Request::createFromGlobals();
+ $this->assertEquals('bar1', $request->query->get('foo1'), '::fromGlobals() uses values from $_GET');
+ $this->assertEquals('bar2', $request->request->get('foo2'), '::fromGlobals() uses values from $_POST');
+ $this->assertEquals('bar3', $request->cookies->get('foo3'), '::fromGlobals() uses values from $_COOKIE');
+ $this->assertEquals(array('bar4'), $request->files->get('foo4'), '::fromGlobals() uses values from $_FILES');
+ $this->assertEquals('bar5', $request->server->get('foo5'), '::fromGlobals() uses values from $_SERVER');
+
+ unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']);
+
+ $_SERVER['REQUEST_METHOD'] = $method;
+ $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ $request = RequestContentProxy::createFromGlobals();
+ $this->assertEquals($normalizedMethod, $request->getMethod());
+ $this->assertEquals('mycontent', $request->request->get('content'));
+
+ unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']);
+
+ Request::createFromGlobals();
+ Request::enableHttpMethodParameterOverride();
+ $_POST['_method'] = $method;
+ $_POST['foo6'] = 'bar6';
+ $_SERVER['REQUEST_METHOD'] = 'PoSt';
+ $request = Request::createFromGlobals();
+ $this->assertEquals($normalizedMethod, $request->getMethod());
+ $this->assertEquals('POST', $request->getRealMethod());
+ $this->assertEquals('bar6', $request->request->get('foo6'));
+
+ unset($_POST['_method'], $_POST['foo6'], $_SERVER['REQUEST_METHOD']);
+ $this->disableHttpMethodParameterOverride();
+ }
+
+ public function testOverrideGlobals()
+ {
+ $request = new Request();
+ $request->initialize(array('foo' => 'bar'));
+
+ // as the Request::overrideGlobals really work, it erase $_SERVER, so we must backup it
+ $server = $_SERVER;
+
+ $request->overrideGlobals();
+
+ $this->assertEquals(array('foo' => 'bar'), $_GET);
+
+ $request->initialize(array(), array('foo' => 'bar'));
+ $request->overrideGlobals();
+
+ $this->assertEquals(array('foo' => 'bar'), $_POST);
+
+ $this->assertArrayNotHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER);
+
+ $request->headers->set('X_FORWARDED_PROTO', 'https');
+
+ Request::setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_ALL);
+ $this->assertFalse($request->isSecure());
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertTrue($request->isSecure());
+
+ $request->overrideGlobals();
+
+ $this->assertArrayHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER);
+
+ $request->headers->set('CONTENT_TYPE', 'multipart/form-data');
+ $request->headers->set('CONTENT_LENGTH', 12345);
+
+ $request->overrideGlobals();
+
+ $this->assertArrayHasKey('CONTENT_TYPE', $_SERVER);
+ $this->assertArrayHasKey('CONTENT_LENGTH', $_SERVER);
+
+ $request->initialize(array('foo' => 'bar', 'baz' => 'foo'));
+ $request->query->remove('baz');
+
+ $request->overrideGlobals();
+
+ $this->assertEquals(array('foo' => 'bar'), $_GET);
+ $this->assertEquals('foo=bar', $_SERVER['QUERY_STRING']);
+ $this->assertEquals('foo=bar', $request->server->get('QUERY_STRING'));
+
+ // restore initial $_SERVER array
+ $_SERVER = $server;
+ }
+
+ public function testGetScriptName()
+ {
+ $request = new Request();
+ $this->assertEquals('', $request->getScriptName());
+
+ $server = array();
+ $server['SCRIPT_NAME'] = '/index.php';
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('/index.php', $request->getScriptName());
+
+ $server = array();
+ $server['ORIG_SCRIPT_NAME'] = '/frontend.php';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('/frontend.php', $request->getScriptName());
+
+ $server = array();
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['ORIG_SCRIPT_NAME'] = '/frontend.php';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('/index.php', $request->getScriptName());
+ }
+
+ public function testGetBasePath()
+ {
+ $request = new Request();
+ $this->assertEquals('', $request->getBasePath());
+
+ $server = array();
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+ $this->assertEquals('', $request->getBasePath());
+
+ $server = array();
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('', $request->getBasePath());
+
+ $server = array();
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+ $server['PHP_SELF'] = '/index.php';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('', $request->getBasePath());
+
+ $server = array();
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+ $server['ORIG_SCRIPT_NAME'] = '/index.php';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('', $request->getBasePath());
+ }
+
+ public function testGetPathInfo()
+ {
+ $request = new Request();
+ $this->assertEquals('/', $request->getPathInfo());
+
+ $server = array();
+ $server['REQUEST_URI'] = '/path/info';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('/path/info', $request->getPathInfo());
+
+ $server = array();
+ $server['REQUEST_URI'] = '/path%20test/info';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('/path%20test/info', $request->getPathInfo());
+
+ $server = array();
+ $server['REQUEST_URI'] = '?a=b';
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals('/', $request->getPathInfo());
+ }
+
+ public function testGetParameterPrecedence()
+ {
+ $request = new Request();
+ $request->attributes->set('foo', 'attr');
+ $request->query->set('foo', 'query');
+ $request->request->set('foo', 'body');
+
+ $this->assertSame('attr', $request->get('foo'));
+
+ $request->attributes->remove('foo');
+ $this->assertSame('query', $request->get('foo'));
+
+ $request->query->remove('foo');
+ $this->assertSame('body', $request->get('foo'));
+
+ $request->request->remove('foo');
+ $this->assertNull($request->get('foo'));
+ }
+
+ public function testGetPreferredLanguage()
+ {
+ $request = new Request();
+ $this->assertNull($request->getPreferredLanguage());
+ $this->assertNull($request->getPreferredLanguage(array()));
+ $this->assertEquals('fr', $request->getPreferredLanguage(array('fr')));
+ $this->assertEquals('fr', $request->getPreferredLanguage(array('fr', 'en')));
+ $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'fr')));
+ $this->assertEquals('fr-ch', $request->getPreferredLanguage(array('fr-ch', 'fr-fr')));
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+ $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'en-us')));
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+ $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en')));
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8');
+ $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en')));
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, fr-fr; q=0.6, fr; q=0.5');
+ $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en')));
+ }
+
+ public function testIsXmlHttpRequest()
+ {
+ $request = new Request();
+ $this->assertFalse($request->isXmlHttpRequest());
+
+ $request->headers->set('X-Requested-With', 'XMLHttpRequest');
+ $this->assertTrue($request->isXmlHttpRequest());
+
+ $request->headers->remove('X-Requested-With');
+ $this->assertFalse($request->isXmlHttpRequest());
+ }
+
+ /**
+ * @requires extension intl
+ */
+ public function testIntlLocale()
+ {
+ $request = new Request();
+
+ $request->setDefaultLocale('fr');
+ $this->assertEquals('fr', $request->getLocale());
+ $this->assertEquals('fr', \Locale::getDefault());
+
+ $request->setLocale('en');
+ $this->assertEquals('en', $request->getLocale());
+ $this->assertEquals('en', \Locale::getDefault());
+
+ $request->setDefaultLocale('de');
+ $this->assertEquals('en', $request->getLocale());
+ $this->assertEquals('en', \Locale::getDefault());
+ }
+
+ public function testGetCharsets()
+ {
+ $request = new Request();
+ $this->assertEquals(array(), $request->getCharsets());
+ $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6');
+ $this->assertEquals(array(), $request->getCharsets()); // testing caching
+
+ $request = new Request();
+ $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6');
+ $this->assertEquals(array('ISO-8859-1', 'US-ASCII', 'UTF-8', 'ISO-10646-UCS-2'), $request->getCharsets());
+
+ $request = new Request();
+ $request->headers->set('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7');
+ $this->assertEquals(array('ISO-8859-1', 'utf-8', '*'), $request->getCharsets());
+ }
+
+ public function testGetEncodings()
+ {
+ $request = new Request();
+ $this->assertEquals(array(), $request->getEncodings());
+ $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch');
+ $this->assertEquals(array(), $request->getEncodings()); // testing caching
+
+ $request = new Request();
+ $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch');
+ $this->assertEquals(array('gzip', 'deflate', 'sdch'), $request->getEncodings());
+
+ $request = new Request();
+ $request->headers->set('Accept-Encoding', 'gzip;q=0.4,deflate;q=0.9,compress;q=0.7');
+ $this->assertEquals(array('deflate', 'compress', 'gzip'), $request->getEncodings());
+ }
+
+ public function testGetAcceptableContentTypes()
+ {
+ $request = new Request();
+ $this->assertEquals(array(), $request->getAcceptableContentTypes());
+ $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*');
+ $this->assertEquals(array(), $request->getAcceptableContentTypes()); // testing caching
+
+ $request = new Request();
+ $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*');
+ $this->assertEquals(array('application/vnd.wap.wmlscriptc', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/xhtml+xml', 'text/html', 'multipart/mixed', '*/*'), $request->getAcceptableContentTypes());
+ }
+
+ public function testGetLanguages()
+ {
+ $request = new Request();
+ $this->assertEquals(array(), $request->getLanguages());
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+ $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages());
+ $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages());
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.6, en; q=0.8');
+ $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test out of order qvalues
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en, en-us');
+ $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test equal weighting without qvalues
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh; q=0.6, en, en-us; q=0.6');
+ $this->assertEquals(array('en', 'zh', 'en_US'), $request->getLanguages()); // Test equal weighting with qvalues
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, i-cherokee; q=0.6');
+ $this->assertEquals(array('zh', 'cherokee'), $request->getLanguages());
+ }
+
+ public function testGetRequestFormat()
+ {
+ $request = new Request();
+ $this->assertEquals('html', $request->getRequestFormat());
+
+ // Ensure that setting different default values over time is possible,
+ // aka. setRequestFormat determines the state.
+ $this->assertEquals('json', $request->getRequestFormat('json'));
+ $this->assertEquals('html', $request->getRequestFormat('html'));
+
+ $request = new Request();
+ $this->assertNull($request->getRequestFormat(null));
+
+ $request = new Request();
+ $this->assertNull($request->setRequestFormat('foo'));
+ $this->assertEquals('foo', $request->getRequestFormat(null));
+
+ $request = new Request(array('_format' => 'foo'));
+ $this->assertEquals('html', $request->getRequestFormat());
+ }
+
+ public function testHasSession()
+ {
+ $request = new Request();
+
+ $this->assertFalse($request->hasSession());
+ $request->setSession(new Session(new MockArraySessionStorage()));
+ $this->assertTrue($request->hasSession());
+ }
+
+ public function testGetSession()
+ {
+ $request = new Request();
+
+ $request->setSession(new Session(new MockArraySessionStorage()));
+ $this->assertTrue($request->hasSession());
+
+ $session = $request->getSession();
+ $this->assertObjectHasAttribute('storage', $session);
+ $this->assertObjectHasAttribute('flashName', $session);
+ $this->assertObjectHasAttribute('attributeName', $session);
+ }
+
+ public function testHasPreviousSession()
+ {
+ $request = new Request();
+
+ $this->assertFalse($request->hasPreviousSession());
+ $request->cookies->set('MOCKSESSID', 'foo');
+ $this->assertFalse($request->hasPreviousSession());
+ $request->setSession(new Session(new MockArraySessionStorage()));
+ $this->assertTrue($request->hasPreviousSession());
+ }
+
+ public function testToString()
+ {
+ $request = new Request();
+
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+ $request->cookies->set('Foo', 'Bar');
+
+ $asString = (string) $request;
+
+ $this->assertContains('Accept-Language: zh, en-us; q=0.8, en; q=0.6', $asString);
+ $this->assertContains('Cookie: Foo=Bar', $asString);
+
+ $request->cookies->set('Another', 'Cookie');
+
+ $asString = (string) $request;
+
+ $this->assertContains('Cookie: Foo=Bar; Another=Cookie', $asString);
+ }
+
+ public function testIsMethod()
+ {
+ $request = new Request();
+ $request->setMethod('POST');
+ $this->assertTrue($request->isMethod('POST'));
+ $this->assertTrue($request->isMethod('post'));
+ $this->assertFalse($request->isMethod('GET'));
+ $this->assertFalse($request->isMethod('get'));
+
+ $request->setMethod('GET');
+ $this->assertTrue($request->isMethod('GET'));
+ $this->assertTrue($request->isMethod('get'));
+ $this->assertFalse($request->isMethod('POST'));
+ $this->assertFalse($request->isMethod('post'));
+ }
+
+ /**
+ * @dataProvider getBaseUrlData
+ */
+ public function testGetBaseUrl($uri, $server, $expectedBaseUrl, $expectedPathInfo)
+ {
+ $request = Request::create($uri, 'GET', array(), array(), array(), $server);
+
+ $this->assertSame($expectedBaseUrl, $request->getBaseUrl(), 'baseUrl');
+ $this->assertSame($expectedPathInfo, $request->getPathInfo(), 'pathInfo');
+ }
+
+ public function getBaseUrlData()
+ {
+ return array(
+ array(
+ '/fruit/strawberry/1234index.php/blah',
+ array(
+ 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/fruit/index.php',
+ 'SCRIPT_NAME' => '/fruit/index.php',
+ 'PHP_SELF' => '/fruit/index.php',
+ ),
+ '/fruit',
+ '/strawberry/1234index.php/blah',
+ ),
+ array(
+ '/fruit/strawberry/1234index.php/blah',
+ array(
+ 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/index.php',
+ 'SCRIPT_NAME' => '/index.php',
+ 'PHP_SELF' => '/index.php',
+ ),
+ '',
+ '/fruit/strawberry/1234index.php/blah',
+ ),
+ array(
+ '/foo%20bar/',
+ array(
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+ 'SCRIPT_NAME' => '/foo bar/app.php',
+ 'PHP_SELF' => '/foo bar/app.php',
+ ),
+ '/foo%20bar',
+ '/',
+ ),
+ array(
+ '/foo%20bar/home',
+ array(
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+ 'SCRIPT_NAME' => '/foo bar/app.php',
+ 'PHP_SELF' => '/foo bar/app.php',
+ ),
+ '/foo%20bar',
+ '/home',
+ ),
+ array(
+ '/foo%20bar/app.php/home',
+ array(
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+ 'SCRIPT_NAME' => '/foo bar/app.php',
+ 'PHP_SELF' => '/foo bar/app.php',
+ ),
+ '/foo%20bar/app.php',
+ '/home',
+ ),
+ array(
+ '/foo%20bar/app.php/home%3Dbaz',
+ array(
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+ 'SCRIPT_NAME' => '/foo bar/app.php',
+ 'PHP_SELF' => '/foo bar/app.php',
+ ),
+ '/foo%20bar/app.php',
+ '/home%3Dbaz',
+ ),
+ array(
+ '/foo/bar+baz',
+ array(
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app.php',
+ 'SCRIPT_NAME' => '/foo/app.php',
+ 'PHP_SELF' => '/foo/app.php',
+ ),
+ '/foo',
+ '/bar+baz',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider urlencodedStringPrefixData
+ */
+ public function testUrlencodedStringPrefix($string, $prefix, $expect)
+ {
+ $request = new Request();
+
+ $me = new \ReflectionMethod($request, 'getUrlencodedPrefix');
+ $me->setAccessible(true);
+
+ $this->assertSame($expect, $me->invoke($request, $string, $prefix));
+ }
+
+ public function urlencodedStringPrefixData()
+ {
+ return array(
+ array('foo', 'foo', 'foo'),
+ array('fo%6f', 'foo', 'fo%6f'),
+ array('foo/bar', 'foo', 'foo'),
+ array('fo%6f/bar', 'foo', 'fo%6f'),
+ array('f%6f%6f/bar', 'foo', 'f%6f%6f'),
+ array('%66%6F%6F/bar', 'foo', '%66%6F%6F'),
+ array('fo+o/bar', 'fo+o', 'fo+o'),
+ array('fo%2Bo/bar', 'fo+o', 'fo%2Bo'),
+ );
+ }
+
+ private function disableHttpMethodParameterOverride()
+ {
+ $class = new \ReflectionClass('Symfony\\Component\\HttpFoundation\\Request');
+ $property = $class->getProperty('httpMethodParameterOverride');
+ $property->setAccessible(true);
+ $property->setValue(false);
+ }
+
+ private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies)
+ {
+ $request = new Request();
+
+ $server = array('REMOTE_ADDR' => $remoteAddr);
+ if (null !== $httpForwardedFor) {
+ $server['HTTP_X_FORWARDED_FOR'] = $httpForwardedFor;
+ }
+
+ if ($trustedProxies) {
+ Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL);
+ }
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ return $request;
+ }
+
+ private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies)
+ {
+ $request = new Request();
+
+ $server = array('REMOTE_ADDR' => $remoteAddr);
+
+ if (null !== $httpForwarded) {
+ $server['HTTP_FORWARDED'] = $httpForwarded;
+ }
+
+ if ($trustedProxies) {
+ Request::setTrustedProxies($trustedProxies, Request::HEADER_FORWARDED);
+ }
+
+ $request->initialize(array(), array(), array(), array(), array(), $server);
+
+ return $request;
+ }
+
+ public function testTrustedProxiesXForwardedFor()
+ {
+ $request = Request::create('http://example.com/');
+ $request->server->set('REMOTE_ADDR', '3.3.3.3');
+ $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
+ $request->headers->set('X_FORWARDED_HOST', 'foo.example.com:1234, real.example.com:8080');
+ $request->headers->set('X_FORWARDED_PROTO', 'https');
+ $request->headers->set('X_FORWARDED_PORT', 443);
+
+ // no trusted proxies
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // disabling proxy trusting
+ Request::setTrustedProxies(array(), Request::HEADER_X_FORWARDED_ALL);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // request is forwarded by a non-trusted proxy
+ Request::setTrustedProxies(array('2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // trusted proxy via setTrustedProxies()
+ Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+ $this->assertEquals('1.1.1.1', $request->getClientIp());
+ $this->assertEquals('foo.example.com', $request->getHost());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ // trusted proxy via setTrustedProxies()
+ Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // check various X_FORWARDED_PROTO header values
+ Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+ $request->headers->set('X_FORWARDED_PROTO', 'ssl');
+ $this->assertTrue($request->isSecure());
+
+ $request->headers->set('X_FORWARDED_PROTO', 'https, http');
+ $this->assertTrue($request->isSecure());
+ }
+
+ /**
+ * @group legacy
+ * @expectedDeprecation The "Symfony\Component\HttpFoundation\Request::setTrustedHeaderName()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.
+ */
+ public function testLegacyTrustedProxies()
+ {
+ $request = Request::create('http://example.com/');
+ $request->server->set('REMOTE_ADDR', '3.3.3.3');
+ $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
+ $request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080');
+ $request->headers->set('X_FORWARDED_PROTO', 'https');
+ $request->headers->set('X_FORWARDED_PORT', 443);
+ $request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4');
+ $request->headers->set('X_MY_HOST', 'my.example.com');
+ $request->headers->set('X_MY_PROTO', 'http');
+ $request->headers->set('X_MY_PORT', 81);
+
+ Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL);
+
+ // custom header names
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_MY_FOR');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_MY_HOST');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_MY_PORT');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_MY_PROTO');
+ $this->assertEquals('4.4.4.4', $request->getClientIp());
+ $this->assertEquals('my.example.com', $request->getHost());
+ $this->assertEquals(81, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // disabling via empty header names
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, null);
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, null);
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, null);
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, null);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ //reset
+ Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO');
+ }
+
+ public function testTrustedProxiesForwarded()
+ {
+ $request = Request::create('http://example.com/');
+ $request->server->set('REMOTE_ADDR', '3.3.3.3');
+ $request->headers->set('FORWARDED', 'for=1.1.1.1, host=foo.example.com:8080, proto=https, for=2.2.2.2, host=real.example.com:8080');
+
+ // no trusted proxies
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // disabling proxy trusting
+ Request::setTrustedProxies(array(), Request::HEADER_FORWARDED);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // request is forwarded by a non-trusted proxy
+ Request::setTrustedProxies(array('2.2.2.2'), Request::HEADER_FORWARDED);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // trusted proxy via setTrustedProxies()
+ Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_FORWARDED);
+ $this->assertEquals('1.1.1.1', $request->getClientIp());
+ $this->assertEquals('foo.example.com', $request->getHost());
+ $this->assertEquals(8080, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ // trusted proxy via setTrustedProxies()
+ Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'), Request::HEADER_FORWARDED);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // check various X_FORWARDED_PROTO header values
+ Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_FORWARDED);
+ $request->headers->set('FORWARDED', 'proto=ssl');
+ $this->assertTrue($request->isSecure());
+
+ $request->headers->set('FORWARDED', 'proto=https, proto=http');
+ $this->assertTrue($request->isSecure());
+ }
+
+ /**
+ * @group legacy
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetTrustedProxiesInvalidHeaderName()
+ {
+ Request::create('http://example.com/');
+ Request::setTrustedHeaderName('bogus name', 'X_MY_FOR');
+ }
+
+ /**
+ * @group legacy
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetTrustedProxiesInvalidHeaderName()
+ {
+ Request::create('http://example.com/');
+ Request::getTrustedHeaderName('bogus name');
+ }
+
+ /**
+ * @dataProvider iisRequestUriProvider
+ */
+ public function testIISRequestUri($headers, $server, $expectedRequestUri)
+ {
+ $request = new Request();
+ $request->headers->replace($headers);
+ $request->server->replace($server);
+
+ $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct');
+
+ $subRequestUri = '/bar/foo';
+ $subRequest = Request::create($subRequestUri, 'get', array(), array(), array(), $request->server->all());
+ $this->assertEquals($subRequestUri, $subRequest->getRequestUri(), '->getRequestUri() is correct in sub request');
+ }
+
+ public function iisRequestUriProvider()
+ {
+ return array(
+ array(
+ array(
+ 'X_ORIGINAL_URL' => '/foo/bar',
+ ),
+ array(),
+ '/foo/bar',
+ ),
+ array(
+ array(
+ 'X_REWRITE_URL' => '/foo/bar',
+ ),
+ array(),
+ '/foo/bar',
+ ),
+ array(
+ array(),
+ array(
+ 'IIS_WasUrlRewritten' => '1',
+ 'UNENCODED_URL' => '/foo/bar',
+ ),
+ '/foo/bar',
+ ),
+ array(
+ array(
+ 'X_ORIGINAL_URL' => '/foo/bar',
+ ),
+ array(
+ 'HTTP_X_ORIGINAL_URL' => '/foo/bar',
+ ),
+ '/foo/bar',
+ ),
+ array(
+ array(
+ 'X_ORIGINAL_URL' => '/foo/bar',
+ ),
+ array(
+ 'IIS_WasUrlRewritten' => '1',
+ 'UNENCODED_URL' => '/foo/bar',
+ ),
+ '/foo/bar',
+ ),
+ array(
+ array(
+ 'X_ORIGINAL_URL' => '/foo/bar',
+ ),
+ array(
+ 'HTTP_X_ORIGINAL_URL' => '/foo/bar',
+ 'IIS_WasUrlRewritten' => '1',
+ 'UNENCODED_URL' => '/foo/bar',
+ ),
+ '/foo/bar',
+ ),
+ array(
+ array(),
+ array(
+ 'ORIG_PATH_INFO' => '/foo/bar',
+ ),
+ '/foo/bar',
+ ),
+ array(
+ array(),
+ array(
+ 'ORIG_PATH_INFO' => '/foo/bar',
+ 'QUERY_STRING' => 'foo=bar',
+ ),
+ '/foo/bar?foo=bar',
+ ),
+ );
+ }
+
+ public function testTrustedHosts()
+ {
+ // create a request
+ $request = Request::create('/');
+
+ // no trusted host set -> no host check
+ $request->headers->set('host', 'evil.com');
+ $this->assertEquals('evil.com', $request->getHost());
+
+ // add a trusted domain and all its subdomains
+ Request::setTrustedHosts(array('^([a-z]{9}\.)?trusted\.com$'));
+
+ // untrusted host
+ $request->headers->set('host', 'evil.com');
+ try {
+ $request->getHost();
+ $this->fail('Request::getHost() should throw an exception when host is not trusted.');
+ } catch (SuspiciousOperationException $e) {
+ $this->assertEquals('Untrusted Host "evil.com".', $e->getMessage());
+ }
+
+ // trusted hosts
+ $request->headers->set('host', 'trusted.com');
+ $this->assertEquals('trusted.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+
+ $request->server->set('HTTPS', true);
+ $request->headers->set('host', 'trusted.com');
+ $this->assertEquals('trusted.com', $request->getHost());
+ $this->assertEquals(443, $request->getPort());
+ $request->server->set('HTTPS', false);
+
+ $request->headers->set('host', 'trusted.com:8000');
+ $this->assertEquals('trusted.com', $request->getHost());
+ $this->assertEquals(8000, $request->getPort());
+
+ $request->headers->set('host', 'subdomain.trusted.com');
+ $this->assertEquals('subdomain.trusted.com', $request->getHost());
+
+ // reset request for following tests
+ Request::setTrustedHosts(array());
+ }
+
+ public function testFactory()
+ {
+ Request::setFactory(function (array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) {
+ return new NewRequest();
+ });
+
+ $this->assertEquals('foo', Request::create('/')->getFoo());
+
+ Request::setFactory(null);
+ }
+
+ /**
+ * @dataProvider getLongHostNames
+ */
+ public function testVeryLongHosts($host)
+ {
+ $start = microtime(true);
+
+ $request = Request::create('/');
+ $request->headers->set('host', $host);
+ $this->assertEquals($host, $request->getHost());
+ $this->assertLessThan(5, microtime(true) - $start);
+ }
+
+ /**
+ * @dataProvider getHostValidities
+ */
+ public function testHostValidity($host, $isValid, $expectedHost = null, $expectedPort = null)
+ {
+ $request = Request::create('/');
+ $request->headers->set('host', $host);
+
+ if ($isValid) {
+ $this->assertSame($expectedHost ?: $host, $request->getHost());
+ if ($expectedPort) {
+ $this->assertSame($expectedPort, $request->getPort());
+ }
+ } else {
+ if (method_exists($this, 'expectException')) {
+ $this->expectException(SuspiciousOperationException::class);
+ $this->expectExceptionMessage('Invalid Host');
+ } else {
+ $this->setExpectedException(SuspiciousOperationException::class, 'Invalid Host');
+ }
+
+ $request->getHost();
+ }
+ }
+
+ public function getHostValidities()
+ {
+ return array(
+ array('.a', false),
+ array('a..', false),
+ array('a.', true),
+ array("\xE9", false),
+ array('[::1]', true),
+ array('[::1]:80', true, '[::1]', 80),
+ array(str_repeat('.', 101), false),
+ );
+ }
+
+ public function getLongHostNames()
+ {
+ return array(
+ array('a'.str_repeat('.a', 40000)),
+ array(str_repeat(':', 101)),
+ );
+ }
+
+ /**
+ * @dataProvider methodIdempotentProvider
+ */
+ public function testMethodIdempotent($method, $idempotent)
+ {
+ $request = new Request();
+ $request->setMethod($method);
+ $this->assertEquals($idempotent, $request->isMethodIdempotent());
+ }
+
+ public function methodIdempotentProvider()
+ {
+ return array(
+ array('HEAD', true),
+ array('GET', true),
+ array('POST', false),
+ array('PUT', true),
+ array('PATCH', false),
+ array('DELETE', true),
+ array('PURGE', true),
+ array('OPTIONS', true),
+ array('TRACE', true),
+ array('CONNECT', false),
+ );
+ }
+
+ /**
+ * @dataProvider methodSafeProvider
+ */
+ public function testMethodSafe($method, $safe)
+ {
+ $request = new Request();
+ $request->setMethod($method);
+ $this->assertEquals($safe, $request->isMethodSafe(false));
+ }
+
+ public function methodSafeProvider()
+ {
+ return array(
+ array('HEAD', true),
+ array('GET', true),
+ array('POST', false),
+ array('PUT', false),
+ array('PATCH', false),
+ array('DELETE', false),
+ array('PURGE', false),
+ array('OPTIONS', true),
+ array('TRACE', true),
+ array('CONNECT', false),
+ );
+ }
+
+ /**
+ * @group legacy
+ * @expectedDeprecation Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.
+ */
+ public function testMethodSafeChecksCacheable()
+ {
+ $request = new Request();
+ $request->setMethod('OPTIONS');
+ $this->assertFalse($request->isMethodSafe());
+ }
+
+ /**
+ * @dataProvider methodCacheableProvider
+ */
+ public function testMethodCacheable($method, $cacheable)
+ {
+ $request = new Request();
+ $request->setMethod($method);
+ $this->assertEquals($cacheable, $request->isMethodCacheable());
+ }
+
+ public function methodCacheableProvider()
+ {
+ return array(
+ array('HEAD', true),
+ array('GET', true),
+ array('POST', false),
+ array('PUT', false),
+ array('PATCH', false),
+ array('DELETE', false),
+ array('PURGE', false),
+ array('OPTIONS', false),
+ array('TRACE', false),
+ array('CONNECT', false),
+ );
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testGetTrustedHeaderName()
+ {
+ Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_X_FORWARDED_ALL);
+
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+ $this->assertSame('X_FORWARDED_FOR', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+ $this->assertSame('X_FORWARDED_HOST', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+ $this->assertSame('X_FORWARDED_PORT', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+ $this->assertSame('X_FORWARDED_PROTO', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+ Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED);
+
+ $this->assertSame('FORWARDED', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+ Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'A');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'B');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'C');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'D');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'E');
+
+ Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED);
+
+ $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+ Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_X_FORWARDED_ALL);
+
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+ $this->assertSame('B', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+ $this->assertSame('C', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+ $this->assertSame('D', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+ $this->assertSame('E', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+ Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED);
+
+ $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+
+ //reset
+ Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO');
+ }
+
+ /**
+ * @dataProvider protocolVersionProvider
+ */
+ public function testProtocolVersion($serverProtocol, $trustedProxy, $via, $expected)
+ {
+ if ($trustedProxy) {
+ Request::setTrustedProxies(array('1.1.1.1'), -1);
+ }
+
+ $request = new Request();
+ $request->server->set('SERVER_PROTOCOL', $serverProtocol);
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $request->headers->set('Via', $via);
+
+ $this->assertSame($expected, $request->getProtocolVersion());
+ }
+
+ public function protocolVersionProvider()
+ {
+ return array(
+ 'untrusted without via' => array('HTTP/2.0', false, '', 'HTTP/2.0'),
+ 'untrusted with via' => array('HTTP/2.0', false, '1.0 fred, 1.1 nowhere.com (Apache/1.1)', 'HTTP/2.0'),
+ 'trusted without via' => array('HTTP/2.0', true, '', 'HTTP/2.0'),
+ 'trusted with via' => array('HTTP/2.0', true, '1.0 fred, 1.1 nowhere.com (Apache/1.1)', 'HTTP/1.0'),
+ 'trusted with via and protocol name' => array('HTTP/2.0', true, 'HTTP/1.0 fred, HTTP/1.1 nowhere.com (Apache/1.1)', 'HTTP/1.0'),
+ 'trusted with broken via' => array('HTTP/2.0', true, 'HTTP/1^0 foo', 'HTTP/2.0'),
+ 'trusted with partially-broken via' => array('HTTP/2.0', true, '1.0 fred, foo', 'HTTP/1.0'),
+ );
+ }
+
+ public function nonstandardRequestsData()
+ {
+ return array(
+ array('', '', '/', 'http://host:8080/', ''),
+ array('/', '', '/', 'http://host:8080/', ''),
+
+ array('hello/app.php/x', '', '/x', 'http://host:8080/hello/app.php/x', '/hello', '/hello/app.php'),
+ array('/hello/app.php/x', '', '/x', 'http://host:8080/hello/app.php/x', '/hello', '/hello/app.php'),
+
+ array('', 'a=b', '/', 'http://host:8080/?a=b'),
+ array('?a=b', 'a=b', '/', 'http://host:8080/?a=b'),
+ array('/?a=b', 'a=b', '/', 'http://host:8080/?a=b'),
+
+ array('x', 'a=b', '/x', 'http://host:8080/x?a=b'),
+ array('x?a=b', 'a=b', '/x', 'http://host:8080/x?a=b'),
+ array('/x?a=b', 'a=b', '/x', 'http://host:8080/x?a=b'),
+
+ array('hello/x', '', '/x', 'http://host:8080/hello/x', '/hello'),
+ array('/hello/x', '', '/x', 'http://host:8080/hello/x', '/hello'),
+
+ array('hello/app.php/x', 'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'),
+ array('hello/app.php/x?a=b', 'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'),
+ array('/hello/app.php/x?a=b', 'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'),
+ );
+ }
+
+ /**
+ * @dataProvider nonstandardRequestsData
+ */
+ public function testNonstandardRequests($requestUri, $queryString, $expectedPathInfo, $expectedUri, $expectedBasePath = '', $expectedBaseUrl = null)
+ {
+ if (null === $expectedBaseUrl) {
+ $expectedBaseUrl = $expectedBasePath;
+ }
+
+ $server = array(
+ 'HTTP_HOST' => 'host:8080',
+ 'SERVER_PORT' => '8080',
+ 'QUERY_STRING' => $queryString,
+ 'PHP_SELF' => '/hello/app.php',
+ 'SCRIPT_FILENAME' => '/some/path/app.php',
+ 'REQUEST_URI' => $requestUri,
+ );
+
+ $request = new Request(array(), array(), array(), array(), array(), $server);
+
+ $this->assertEquals($expectedPathInfo, $request->getPathInfo());
+ $this->assertEquals($expectedUri, $request->getUri());
+ $this->assertEquals($queryString, $request->getQueryString());
+ $this->assertEquals(8080, $request->getPort());
+ $this->assertEquals('host:8080', $request->getHttpHost());
+ $this->assertEquals($expectedBaseUrl, $request->getBaseUrl());
+ $this->assertEquals($expectedBasePath, $request->getBasePath());
+ }
+}
+
+class RequestContentProxy extends Request
+{
+ public function getContent($asResource = false)
+ {
+ return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent'), '', '&');
+ }
+}
+
+class NewRequest extends Request
+{
+ public function getFoo()
+ {
+ return 'foo';
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php
new file mode 100644
index 0000000..ce85535
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php
@@ -0,0 +1,363 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+use Symfony\Component\HttpFoundation\Cookie;
+
+/**
+ * @group time-sensitive
+ */
+class ResponseHeaderBagTest extends TestCase
+{
+ public function testAllPreserveCase()
+ {
+ $headers = array(
+ 'fOo' => 'BAR',
+ 'ETag' => 'xyzzy',
+ 'Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ==',
+ 'P3P' => 'CP="CAO PSA OUR"',
+ 'WWW-Authenticate' => 'Basic realm="WallyWorld"',
+ 'X-UA-Compatible' => 'IE=edge,chrome=1',
+ 'X-XSS-Protection' => '1; mode=block',
+ );
+
+ $bag = new ResponseHeaderBag($headers);
+ $allPreservedCase = $bag->allPreserveCase();
+
+ foreach (array_keys($headers) as $headerName) {
+ $this->assertArrayHasKey($headerName, $allPreservedCase, '->allPreserveCase() gets all input keys in original case');
+ }
+ }
+
+ public function testCacheControlHeader()
+ {
+ $bag = new ResponseHeaderBag(array());
+ $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+ $bag = new ResponseHeaderBag(array('Cache-Control' => 'public'));
+ $this->assertEquals('public', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+
+ $bag = new ResponseHeaderBag(array('ETag' => 'abcde'));
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('private'));
+ $this->assertTrue($bag->hasCacheControlDirective('must-revalidate'));
+ $this->assertFalse($bag->hasCacheControlDirective('max-age'));
+
+ $bag = new ResponseHeaderBag(array('Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT'));
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(array(
+ 'Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT',
+ 'Cache-Control' => 'max-age=3600',
+ ));
+ $this->assertEquals('max-age=3600, private', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(array('Last-Modified' => 'abcde'));
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(array('Etag' => 'abcde', 'Last-Modified' => 'abcde'));
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(array('cache-control' => 'max-age=100'));
+ $this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(array('cache-control' => 's-maxage=100'));
+ $this->assertEquals('s-maxage=100', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(array('cache-control' => 'private, max-age=100'));
+ $this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(array('cache-control' => 'public, max-age=100'));
+ $this->assertEquals('max-age=100, public', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag();
+ $bag->set('Last-Modified', 'abcde');
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag();
+ $bag->set('Cache-Control', array('public', 'must-revalidate'));
+ $this->assertCount(1, $bag->get('Cache-Control', null, false));
+ $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag();
+ $bag->set('Cache-Control', 'public');
+ $bag->set('Cache-Control', 'must-revalidate', false);
+ $this->assertCount(1, $bag->get('Cache-Control', null, false));
+ $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control'));
+ }
+
+ public function testCacheControlClone()
+ {
+ $headers = array('foo' => 'bar');
+ $bag1 = new ResponseHeaderBag($headers);
+ $bag2 = new ResponseHeaderBag($bag1->allPreserveCase());
+ $this->assertEquals($bag1->allPreserveCase(), $bag2->allPreserveCase());
+ }
+
+ public function testToStringIncludesCookieHeaders()
+ {
+ $bag = new ResponseHeaderBag(array());
+ $bag->setCookie(new Cookie('foo', 'bar'));
+
+ $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag);
+
+ $bag->clearCookie('foo');
+
+ $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; httponly', $bag);
+ }
+
+ public function testClearCookieSecureNotHttpOnly()
+ {
+ $bag = new ResponseHeaderBag(array());
+
+ $bag->clearCookie('foo', '/', null, true, false);
+
+ $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; secure', $bag);
+ }
+
+ public function testReplace()
+ {
+ $bag = new ResponseHeaderBag(array());
+ $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+ $bag->replace(array('Cache-Control' => 'public'));
+ $this->assertEquals('public', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ }
+
+ public function testReplaceWithRemove()
+ {
+ $bag = new ResponseHeaderBag(array());
+ $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+ $bag->remove('Cache-Control');
+ $bag->replace(array());
+ $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+ }
+
+ public function testCookiesWithSameNames()
+ {
+ $bag = new ResponseHeaderBag();
+ $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
+ $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'foo.bar'));
+ $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'bar.foo'));
+ $bag->setCookie(new Cookie('foo', 'bar'));
+
+ $this->assertCount(4, $bag->getCookies());
+ $this->assertEquals('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag->get('set-cookie'));
+ $this->assertEquals(array(
+ 'foo=bar; path=/path/foo; domain=foo.bar; httponly',
+ 'foo=bar; path=/path/bar; domain=foo.bar; httponly',
+ 'foo=bar; path=/path/bar; domain=bar.foo; httponly',
+ 'foo=bar; path=/; httponly',
+ ), $bag->get('set-cookie', null, false));
+
+ $this->assertSetCookieHeader('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag);
+ $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=foo.bar; httponly', $bag);
+ $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=bar.foo; httponly', $bag);
+ $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag);
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+
+ $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/foo']);
+ $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/bar']);
+ $this->assertArrayHasKey('foo', $cookies['bar.foo']['/path/bar']);
+ $this->assertArrayHasKey('foo', $cookies['']['/']);
+ }
+
+ public function testRemoveCookie()
+ {
+ $bag = new ResponseHeaderBag();
+ $this->assertFalse($bag->has('set-cookie'));
+
+ $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
+ $bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar'));
+ $this->assertTrue($bag->has('set-cookie'));
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayHasKey('/path/foo', $cookies['foo.bar']);
+
+ $bag->removeCookie('foo', '/path/foo', 'foo.bar');
+ $this->assertTrue($bag->has('set-cookie'));
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayNotHasKey('/path/foo', $cookies['foo.bar']);
+
+ $bag->removeCookie('bar', '/path/bar', 'foo.bar');
+ $this->assertFalse($bag->has('set-cookie'));
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayNotHasKey('foo.bar', $cookies);
+ }
+
+ public function testRemoveCookieWithNullRemove()
+ {
+ $bag = new ResponseHeaderBag();
+ $bag->setCookie(new Cookie('foo', 'bar', 0));
+ $bag->setCookie(new Cookie('bar', 'foo', 0));
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayHasKey('/', $cookies['']);
+
+ $bag->removeCookie('foo', null);
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayNotHasKey('foo', $cookies['']['/']);
+
+ $bag->removeCookie('bar', null);
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertFalse(isset($cookies['']['/']['bar']));
+ }
+
+ public function testSetCookieHeader()
+ {
+ $bag = new ResponseHeaderBag();
+ $bag->set('set-cookie', 'foo=bar');
+ $this->assertEquals(array(new Cookie('foo', 'bar', 0, '/', null, false, false, true)), $bag->getCookies());
+
+ $bag->set('set-cookie', 'foo2=bar2', false);
+ $this->assertEquals(array(
+ new Cookie('foo', 'bar', 0, '/', null, false, false, true),
+ new Cookie('foo2', 'bar2', 0, '/', null, false, false, true),
+ ), $bag->getCookies());
+
+ $bag->remove('set-cookie');
+ $this->assertEquals(array(), $bag->getCookies());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetCookiesWithInvalidArgument()
+ {
+ $bag = new ResponseHeaderBag();
+
+ $bag->getCookies('invalid_argument');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testMakeDispositionInvalidDisposition()
+ {
+ $headers = new ResponseHeaderBag();
+
+ $headers->makeDisposition('invalid', 'foo.html');
+ }
+
+ /**
+ * @dataProvider provideMakeDisposition
+ */
+ public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected)
+ {
+ $headers = new ResponseHeaderBag();
+
+ $this->assertEquals($expected, $headers->makeDisposition($disposition, $filename, $filenameFallback));
+ }
+
+ public function testToStringDoesntMessUpHeaders()
+ {
+ $headers = new ResponseHeaderBag();
+
+ $headers->set('Location', 'http://www.symfony.com');
+ $headers->set('Content-type', 'text/html');
+
+ (string) $headers;
+
+ $allHeaders = $headers->allPreserveCase();
+ $this->assertEquals(array('http://www.symfony.com'), $allHeaders['Location']);
+ $this->assertEquals(array('text/html'), $allHeaders['Content-type']);
+ }
+
+ public function provideMakeDisposition()
+ {
+ return array(
+ array('attachment', 'foo.html', 'foo.html', 'attachment; filename="foo.html"'),
+ array('attachment', 'foo.html', '', 'attachment; filename="foo.html"'),
+ array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'),
+ array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'),
+ array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'),
+ array('attachment', 'föö.html', 'foo.html', 'attachment; filename="foo.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html'),
+ );
+ }
+
+ /**
+ * @dataProvider provideMakeDispositionFail
+ * @expectedException \InvalidArgumentException
+ */
+ public function testMakeDispositionFail($disposition, $filename)
+ {
+ $headers = new ResponseHeaderBag();
+
+ $headers->makeDisposition($disposition, $filename);
+ }
+
+ public function provideMakeDispositionFail()
+ {
+ return array(
+ array('attachment', 'foo%20bar.html'),
+ array('attachment', 'foo/bar.html'),
+ array('attachment', '/foo.html'),
+ array('attachment', 'foo\bar.html'),
+ array('attachment', '\foo.html'),
+ array('attachment', 'föö.html'),
+ );
+ }
+
+ public function testDateHeaderAddedOnCreation()
+ {
+ $now = time();
+
+ $bag = new ResponseHeaderBag();
+ $this->assertTrue($bag->has('Date'));
+
+ $this->assertEquals($now, $bag->getDate('Date')->getTimestamp());
+ }
+
+ public function testDateHeaderCanBeSetOnCreation()
+ {
+ $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT';
+ $bag = new ResponseHeaderBag(array('Date' => $someDate));
+
+ $this->assertEquals($someDate, $bag->get('Date'));
+ }
+
+ public function testDateHeaderWillBeRecreatedWhenRemoved()
+ {
+ $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT';
+ $bag = new ResponseHeaderBag(array('Date' => $someDate));
+ $bag->remove('Date');
+
+ // a (new) Date header is still present
+ $this->assertTrue($bag->has('Date'));
+ $this->assertNotEquals($someDate, $bag->get('Date'));
+ }
+
+ public function testDateHeaderWillBeRecreatedWhenHeadersAreReplaced()
+ {
+ $bag = new ResponseHeaderBag();
+ $bag->replace(array());
+
+ $this->assertTrue($bag->has('Date'));
+ }
+
+ private function assertSetCookieHeader($expected, ResponseHeaderBag $actual)
+ {
+ $this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/ResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ResponseTest.php
new file mode 100644
index 0000000..350d972
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/ResponseTest.php
@@ -0,0 +1,1003 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * @group time-sensitive
+ */
+class ResponseTest extends ResponseTestCase
+{
+ public function testCreate()
+ {
+ $response = Response::create('foo', 301, array('Foo' => 'bar'));
+
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
+ $this->assertEquals(301, $response->getStatusCode());
+ $this->assertEquals('bar', $response->headers->get('foo'));
+ }
+
+ public function testToString()
+ {
+ $response = new Response();
+ $response = explode("\r\n", $response);
+ $this->assertEquals('HTTP/1.0 200 OK', $response[0]);
+ $this->assertEquals('Cache-Control: no-cache, private', $response[1]);
+ }
+
+ public function testClone()
+ {
+ $response = new Response();
+ $responseClone = clone $response;
+ $this->assertEquals($response, $responseClone);
+ }
+
+ public function testSendHeaders()
+ {
+ $response = new Response();
+ $headers = $response->sendHeaders();
+ $this->assertObjectHasAttribute('headers', $headers);
+ $this->assertObjectHasAttribute('content', $headers);
+ $this->assertObjectHasAttribute('version', $headers);
+ $this->assertObjectHasAttribute('statusCode', $headers);
+ $this->assertObjectHasAttribute('statusText', $headers);
+ $this->assertObjectHasAttribute('charset', $headers);
+ }
+
+ public function testSend()
+ {
+ $response = new Response();
+ $responseSend = $response->send();
+ $this->assertObjectHasAttribute('headers', $responseSend);
+ $this->assertObjectHasAttribute('content', $responseSend);
+ $this->assertObjectHasAttribute('version', $responseSend);
+ $this->assertObjectHasAttribute('statusCode', $responseSend);
+ $this->assertObjectHasAttribute('statusText', $responseSend);
+ $this->assertObjectHasAttribute('charset', $responseSend);
+ }
+
+ public function testGetCharset()
+ {
+ $response = new Response();
+ $charsetOrigin = 'UTF-8';
+ $response->setCharset($charsetOrigin);
+ $charset = $response->getCharset();
+ $this->assertEquals($charsetOrigin, $charset);
+ }
+
+ public function testIsCacheable()
+ {
+ $response = new Response();
+ $this->assertFalse($response->isCacheable());
+ }
+
+ public function testIsCacheableWithErrorCode()
+ {
+ $response = new Response('', 500);
+ $this->assertFalse($response->isCacheable());
+ }
+
+ public function testIsCacheableWithNoStoreDirective()
+ {
+ $response = new Response();
+ $response->headers->set('cache-control', 'private');
+ $this->assertFalse($response->isCacheable());
+ }
+
+ public function testIsCacheableWithSetTtl()
+ {
+ $response = new Response();
+ $response->setTtl(10);
+ $this->assertTrue($response->isCacheable());
+ }
+
+ public function testMustRevalidate()
+ {
+ $response = new Response();
+ $this->assertFalse($response->mustRevalidate());
+ }
+
+ public function testMustRevalidateWithMustRevalidateCacheControlHeader()
+ {
+ $response = new Response();
+ $response->headers->set('cache-control', 'must-revalidate');
+
+ $this->assertTrue($response->mustRevalidate());
+ }
+
+ public function testMustRevalidateWithProxyRevalidateCacheControlHeader()
+ {
+ $response = new Response();
+ $response->headers->set('cache-control', 'proxy-revalidate');
+
+ $this->assertTrue($response->mustRevalidate());
+ }
+
+ public function testSetNotModified()
+ {
+ $response = new Response();
+ $modified = $response->setNotModified();
+ $this->assertObjectHasAttribute('headers', $modified);
+ $this->assertObjectHasAttribute('content', $modified);
+ $this->assertObjectHasAttribute('version', $modified);
+ $this->assertObjectHasAttribute('statusCode', $modified);
+ $this->assertObjectHasAttribute('statusText', $modified);
+ $this->assertObjectHasAttribute('charset', $modified);
+ $this->assertEquals(304, $modified->getStatusCode());
+ }
+
+ public function testIsSuccessful()
+ {
+ $response = new Response();
+ $this->assertTrue($response->isSuccessful());
+ }
+
+ public function testIsNotModified()
+ {
+ $response = new Response();
+ $modified = $response->isNotModified(new Request());
+ $this->assertFalse($modified);
+ }
+
+ public function testIsNotModifiedNotSafe()
+ {
+ $request = Request::create('/homepage', 'POST');
+
+ $response = new Response();
+ $this->assertFalse($response->isNotModified($request));
+ }
+
+ public function testIsNotModifiedLastModified()
+ {
+ $before = 'Sun, 25 Aug 2013 18:32:31 GMT';
+ $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+ $after = 'Sun, 25 Aug 2013 19:33:31 GMT';
+
+ $request = new Request();
+ $request->headers->set('If-Modified-Since', $modified);
+
+ $response = new Response();
+
+ $response->headers->set('Last-Modified', $modified);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('Last-Modified', $before);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('Last-Modified', $after);
+ $this->assertFalse($response->isNotModified($request));
+
+ $response->headers->set('Last-Modified', '');
+ $this->assertFalse($response->isNotModified($request));
+ }
+
+ public function testIsNotModifiedEtag()
+ {
+ $etagOne = 'randomly_generated_etag';
+ $etagTwo = 'randomly_generated_etag_2';
+
+ $request = new Request();
+ $request->headers->set('if_none_match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree'));
+
+ $response = new Response();
+
+ $response->headers->set('ETag', $etagOne);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('ETag', $etagTwo);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('ETag', '');
+ $this->assertFalse($response->isNotModified($request));
+ }
+
+ public function testIsNotModifiedLastModifiedAndEtag()
+ {
+ $before = 'Sun, 25 Aug 2013 18:32:31 GMT';
+ $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+ $after = 'Sun, 25 Aug 2013 19:33:31 GMT';
+ $etag = 'randomly_generated_etag';
+
+ $request = new Request();
+ $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree'));
+ $request->headers->set('If-Modified-Since', $modified);
+
+ $response = new Response();
+
+ $response->headers->set('ETag', $etag);
+ $response->headers->set('Last-Modified', $after);
+ $this->assertFalse($response->isNotModified($request));
+
+ $response->headers->set('ETag', 'non-existent-etag');
+ $response->headers->set('Last-Modified', $before);
+ $this->assertFalse($response->isNotModified($request));
+
+ $response->headers->set('ETag', $etag);
+ $response->headers->set('Last-Modified', $modified);
+ $this->assertTrue($response->isNotModified($request));
+ }
+
+ public function testIsNotModifiedIfModifiedSinceAndEtagWithoutLastModified()
+ {
+ $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+ $etag = 'randomly_generated_etag';
+
+ $request = new Request();
+ $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree'));
+ $request->headers->set('If-Modified-Since', $modified);
+
+ $response = new Response();
+
+ $response->headers->set('ETag', $etag);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('ETag', 'non-existent-etag');
+ $this->assertFalse($response->isNotModified($request));
+ }
+
+ public function testIsValidateable()
+ {
+ $response = new Response('', 200, array('Last-Modified' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)));
+ $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if Last-Modified is present');
+
+ $response = new Response('', 200, array('ETag' => '"12345"'));
+ $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if ETag is present');
+
+ $response = new Response();
+ $this->assertFalse($response->isValidateable(), '->isValidateable() returns false when no validator is present');
+ }
+
+ public function testGetDate()
+ {
+ $oneHourAgo = $this->createDateTimeOneHourAgo();
+ $response = new Response('', 200, array('Date' => $oneHourAgo->format(DATE_RFC2822)));
+ $date = $response->getDate();
+ $this->assertEquals($oneHourAgo->getTimestamp(), $date->getTimestamp(), '->getDate() returns the Date header if present');
+
+ $response = new Response();
+ $date = $response->getDate();
+ $this->assertEquals(time(), $date->getTimestamp(), '->getDate() returns the current Date if no Date header present');
+
+ $response = new Response('', 200, array('Date' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)));
+ $now = $this->createDateTimeNow();
+ $response->headers->set('Date', $now->format(DATE_RFC2822));
+ $date = $response->getDate();
+ $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the date when the header has been modified');
+
+ $response = new Response('', 200);
+ $now = $this->createDateTimeNow();
+ $response->headers->remove('Date');
+ $date = $response->getDate();
+ $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the current Date when the header has previously been removed');
+ }
+
+ public function testGetMaxAge()
+ {
+ $response = new Response();
+ $response->headers->set('Cache-Control', 's-maxage=600, max-age=0');
+ $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() uses s-maxage cache control directive when present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=600');
+ $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() falls back to max-age when no s-maxage directive present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'must-revalidate');
+ $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822));
+ $this->assertEquals(3600, $response->getMaxAge(), '->getMaxAge() falls back to Expires when no max-age or s-maxage directive present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'must-revalidate');
+ $response->headers->set('Expires', -1);
+ $this->assertEquals('Sat, 01 Jan 00 00:00:00 +0000', $response->getExpires()->format(DATE_RFC822));
+
+ $response = new Response();
+ $this->assertNull($response->getMaxAge(), '->getMaxAge() returns null if no freshness information available');
+ }
+
+ public function testSetSharedMaxAge()
+ {
+ $response = new Response();
+ $response->setSharedMaxAge(20);
+
+ $cacheControl = $response->headers->get('Cache-Control');
+ $this->assertEquals('public, s-maxage=20', $cacheControl);
+ }
+
+ public function testIsPrivate()
+ {
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=100');
+ $response->setPrivate();
+ $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true');
+ $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'public, max-age=100');
+ $response->setPrivate();
+ $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true');
+ $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true');
+ $this->assertFalse($response->headers->hasCacheControlDirective('public'), '->isPrivate() removes the public Cache-Control directive');
+ }
+
+ public function testExpire()
+ {
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=100');
+ $response->expire();
+ $this->assertEquals(100, $response->headers->get('Age'), '->expire() sets the Age to max-age when present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=100, s-maxage=500');
+ $response->expire();
+ $this->assertEquals(500, $response->headers->get('Age'), '->expire() sets the Age to s-maxage when both max-age and s-maxage are present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=5, s-maxage=500');
+ $response->headers->set('Age', '1000');
+ $response->expire();
+ $this->assertEquals(1000, $response->headers->get('Age'), '->expire() does nothing when the response is already stale/expired');
+
+ $response = new Response();
+ $response->expire();
+ $this->assertFalse($response->headers->has('Age'), '->expire() does nothing when the response does not include freshness information');
+
+ $response = new Response();
+ $response->headers->set('Expires', -1);
+ $response->expire();
+ $this->assertNull($response->headers->get('Age'), '->expire() does not set the Age when the response is expired');
+ }
+
+ public function testGetTtl()
+ {
+ $response = new Response();
+ $this->assertNull($response->getTtl(), '->getTtl() returns null when no Expires or Cache-Control headers are present');
+
+ $response = new Response();
+ $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822));
+ $this->assertEquals(3600, $response->getTtl(), '->getTtl() uses the Expires header when no max-age is present');
+
+ $response = new Response();
+ $response->headers->set('Expires', $this->createDateTimeOneHourAgo()->format(DATE_RFC2822));
+ $this->assertLessThan(0, $response->getTtl(), '->getTtl() returns negative values when Expires is in past');
+
+ $response = new Response();
+ $response->headers->set('Expires', $response->getDate()->format(DATE_RFC2822));
+ $response->headers->set('Age', 0);
+ $this->assertSame(0, $response->getTtl(), '->getTtl() correctly handles zero');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=60');
+ $this->assertEquals(60, $response->getTtl(), '->getTtl() uses Cache-Control max-age when present');
+ }
+
+ public function testSetClientTtl()
+ {
+ $response = new Response();
+ $response->setClientTtl(10);
+
+ $this->assertEquals($response->getMaxAge(), $response->getAge() + 10);
+ }
+
+ public function testGetSetProtocolVersion()
+ {
+ $response = new Response();
+
+ $this->assertEquals('1.0', $response->getProtocolVersion());
+
+ $response->setProtocolVersion('1.1');
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ }
+
+ public function testGetVary()
+ {
+ $response = new Response();
+ $this->assertEquals(array(), $response->getVary(), '->getVary() returns an empty array if no Vary header is present');
+
+ $response = new Response();
+ $response->headers->set('Vary', 'Accept-Language');
+ $this->assertEquals(array('Accept-Language'), $response->getVary(), '->getVary() parses a single header name value');
+
+ $response = new Response();
+ $response->headers->set('Vary', 'Accept-Language User-Agent X-Foo');
+ $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by spaces');
+
+ $response = new Response();
+ $response->headers->set('Vary', 'Accept-Language,User-Agent, X-Foo');
+ $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by commas');
+
+ $vary = array('Accept-Language', 'User-Agent', 'X-foo');
+
+ $response = new Response();
+ $response->headers->set('Vary', $vary);
+ $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays');
+
+ $response = new Response();
+ $response->headers->set('Vary', 'Accept-Language, User-Agent, X-foo');
+ $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays');
+ }
+
+ public function testSetVary()
+ {
+ $response = new Response();
+ $response->setVary('Accept-Language');
+ $this->assertEquals(array('Accept-Language'), $response->getVary());
+
+ $response->setVary('Accept-Language, User-Agent');
+ $this->assertEquals(array('Accept-Language', 'User-Agent'), $response->getVary(), '->setVary() replace the vary header by default');
+
+ $response->setVary('X-Foo', false);
+ $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->setVary() doesn\'t wipe out earlier Vary headers if replace is set to false');
+ }
+
+ public function testDefaultContentType()
+ {
+ $headerMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\ResponseHeaderBag')->setMethods(array('set'))->getMock();
+ $headerMock->expects($this->at(0))
+ ->method('set')
+ ->with('Content-Type', 'text/html');
+ $headerMock->expects($this->at(1))
+ ->method('set')
+ ->with('Content-Type', 'text/html; charset=UTF-8');
+
+ $response = new Response('foo');
+ $response->headers = $headerMock;
+
+ $response->prepare(new Request());
+ }
+
+ public function testContentTypeCharset()
+ {
+ $response = new Response();
+ $response->headers->set('Content-Type', 'text/css');
+
+ // force fixContentType() to be called
+ $response->prepare(new Request());
+
+ $this->assertEquals('text/css; charset=UTF-8', $response->headers->get('Content-Type'));
+ }
+
+ public function testPrepareDoesNothingIfContentTypeIsSet()
+ {
+ $response = new Response('foo');
+ $response->headers->set('Content-Type', 'text/plain');
+
+ $response->prepare(new Request());
+
+ $this->assertEquals('text/plain; charset=UTF-8', $response->headers->get('content-type'));
+ }
+
+ public function testPrepareDoesNothingIfRequestFormatIsNotDefined()
+ {
+ $response = new Response('foo');
+
+ $response->prepare(new Request());
+
+ $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type'));
+ }
+
+ public function testPrepareSetContentType()
+ {
+ $response = new Response('foo');
+ $request = Request::create('/');
+ $request->setRequestFormat('json');
+
+ $response->prepare($request);
+
+ $this->assertEquals('application/json', $response->headers->get('content-type'));
+ }
+
+ public function testPrepareRemovesContentForHeadRequests()
+ {
+ $response = new Response('foo');
+ $request = Request::create('/', 'HEAD');
+
+ $length = 12345;
+ $response->headers->set('Content-Length', $length);
+ $response->prepare($request);
+
+ $this->assertEquals('', $response->getContent());
+ $this->assertEquals($length, $response->headers->get('Content-Length'), 'Content-Length should be as if it was GET; see RFC2616 14.13');
+ }
+
+ public function testPrepareRemovesContentForInformationalResponse()
+ {
+ $response = new Response('foo');
+ $request = Request::create('/');
+
+ $response->setContent('content');
+ $response->setStatusCode(101);
+ $response->prepare($request);
+ $this->assertEquals('', $response->getContent());
+ $this->assertFalse($response->headers->has('Content-Type'));
+ $this->assertFalse($response->headers->has('Content-Type'));
+
+ $response->setContent('content');
+ $response->setStatusCode(304);
+ $response->prepare($request);
+ $this->assertEquals('', $response->getContent());
+ $this->assertFalse($response->headers->has('Content-Type'));
+ $this->assertFalse($response->headers->has('Content-Length'));
+ }
+
+ public function testPrepareRemovesContentLength()
+ {
+ $response = new Response('foo');
+ $request = Request::create('/');
+
+ $response->headers->set('Content-Length', 12345);
+ $response->prepare($request);
+ $this->assertEquals(12345, $response->headers->get('Content-Length'));
+
+ $response->headers->set('Transfer-Encoding', 'chunked');
+ $response->prepare($request);
+ $this->assertFalse($response->headers->has('Content-Length'));
+ }
+
+ public function testPrepareSetsPragmaOnHttp10Only()
+ {
+ $request = Request::create('/', 'GET');
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0');
+
+ $response = new Response('foo');
+ $response->prepare($request);
+ $this->assertEquals('no-cache', $response->headers->get('pragma'));
+ $this->assertEquals('-1', $response->headers->get('expires'));
+
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1');
+ $response = new Response('foo');
+ $response->prepare($request);
+ $this->assertFalse($response->headers->has('pragma'));
+ $this->assertFalse($response->headers->has('expires'));
+ }
+
+ public function testSetCache()
+ {
+ $response = new Response();
+ //array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public')
+ try {
+ $response->setCache(array('wrong option' => 'value'));
+ $this->fail('->setCache() throws an InvalidArgumentException if an option is not supported');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('InvalidArgumentException', $e, '->setCache() throws an InvalidArgumentException if an option is not supported');
+ $this->assertContains('"wrong option"', $e->getMessage());
+ }
+
+ $options = array('etag' => '"whatever"');
+ $response->setCache($options);
+ $this->assertEquals($response->getEtag(), '"whatever"');
+
+ $now = $this->createDateTimeNow();
+ $options = array('last_modified' => $now);
+ $response->setCache($options);
+ $this->assertEquals($response->getLastModified()->getTimestamp(), $now->getTimestamp());
+
+ $options = array('max_age' => 100);
+ $response->setCache($options);
+ $this->assertEquals($response->getMaxAge(), 100);
+
+ $options = array('s_maxage' => 200);
+ $response->setCache($options);
+ $this->assertEquals($response->getMaxAge(), 200);
+
+ $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(array('public' => true));
+ $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(array('public' => false));
+ $this->assertFalse($response->headers->hasCacheControlDirective('public'));
+ $this->assertTrue($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(array('private' => true));
+ $this->assertFalse($response->headers->hasCacheControlDirective('public'));
+ $this->assertTrue($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(array('private' => false));
+ $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(array('immutable' => true));
+ $this->assertTrue($response->headers->hasCacheControlDirective('immutable'));
+
+ $response->setCache(array('immutable' => false));
+ $this->assertFalse($response->headers->hasCacheControlDirective('immutable'));
+ }
+
+ public function testSendContent()
+ {
+ $response = new Response('test response rendering', 200);
+
+ ob_start();
+ $response->sendContent();
+ $string = ob_get_clean();
+ $this->assertContains('test response rendering', $string);
+ }
+
+ public function testSetPublic()
+ {
+ $response = new Response();
+ $response->setPublic();
+
+ $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+ }
+
+ public function testSetImmutable()
+ {
+ $response = new Response();
+ $response->setImmutable();
+
+ $this->assertTrue($response->headers->hasCacheControlDirective('immutable'));
+ }
+
+ public function testIsImmutable()
+ {
+ $response = new Response();
+ $response->setImmutable();
+
+ $this->assertTrue($response->isImmutable());
+ }
+
+ public function testSetExpires()
+ {
+ $response = new Response();
+ $response->setExpires(null);
+
+ $this->assertNull($response->getExpires(), '->setExpires() remove the header when passed null');
+
+ $now = $this->createDateTimeNow();
+ $response->setExpires($now);
+
+ $this->assertEquals($response->getExpires()->getTimestamp(), $now->getTimestamp());
+ }
+
+ public function testSetLastModified()
+ {
+ $response = new Response();
+ $response->setLastModified($this->createDateTimeNow());
+ $this->assertNotNull($response->getLastModified());
+
+ $response->setLastModified(null);
+ $this->assertNull($response->getLastModified());
+ }
+
+ public function testIsInvalid()
+ {
+ $response = new Response();
+
+ try {
+ $response->setStatusCode(99);
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertTrue($response->isInvalid());
+ }
+
+ try {
+ $response->setStatusCode(650);
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertTrue($response->isInvalid());
+ }
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isInvalid());
+ }
+
+ /**
+ * @dataProvider getStatusCodeFixtures
+ */
+ public function testSetStatusCode($code, $text, $expectedText)
+ {
+ $response = new Response();
+
+ $response->setStatusCode($code, $text);
+
+ $statusText = new \ReflectionProperty($response, 'statusText');
+ $statusText->setAccessible(true);
+
+ $this->assertEquals($expectedText, $statusText->getValue($response));
+ }
+
+ public function getStatusCodeFixtures()
+ {
+ return array(
+ array('200', null, 'OK'),
+ array('200', false, ''),
+ array('200', 'foo', 'foo'),
+ array('199', null, 'unknown status'),
+ array('199', false, ''),
+ array('199', 'foo', 'foo'),
+ );
+ }
+
+ public function testIsInformational()
+ {
+ $response = new Response('', 100);
+ $this->assertTrue($response->isInformational());
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isInformational());
+ }
+
+ public function testIsRedirectRedirection()
+ {
+ foreach (array(301, 302, 303, 307) as $code) {
+ $response = new Response('', $code);
+ $this->assertTrue($response->isRedirection());
+ $this->assertTrue($response->isRedirect());
+ }
+
+ $response = new Response('', 304);
+ $this->assertTrue($response->isRedirection());
+ $this->assertFalse($response->isRedirect());
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isRedirection());
+ $this->assertFalse($response->isRedirect());
+
+ $response = new Response('', 404);
+ $this->assertFalse($response->isRedirection());
+ $this->assertFalse($response->isRedirect());
+
+ $response = new Response('', 301, array('Location' => '/good-uri'));
+ $this->assertFalse($response->isRedirect('/bad-uri'));
+ $this->assertTrue($response->isRedirect('/good-uri'));
+ }
+
+ public function testIsNotFound()
+ {
+ $response = new Response('', 404);
+ $this->assertTrue($response->isNotFound());
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isNotFound());
+ }
+
+ public function testIsEmpty()
+ {
+ foreach (array(204, 304) as $code) {
+ $response = new Response('', $code);
+ $this->assertTrue($response->isEmpty());
+ }
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isEmpty());
+ }
+
+ public function testIsForbidden()
+ {
+ $response = new Response('', 403);
+ $this->assertTrue($response->isForbidden());
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isForbidden());
+ }
+
+ public function testIsOk()
+ {
+ $response = new Response('', 200);
+ $this->assertTrue($response->isOk());
+
+ $response = new Response('', 404);
+ $this->assertFalse($response->isOk());
+ }
+
+ public function testIsServerOrClientError()
+ {
+ $response = new Response('', 404);
+ $this->assertTrue($response->isClientError());
+ $this->assertFalse($response->isServerError());
+
+ $response = new Response('', 500);
+ $this->assertFalse($response->isClientError());
+ $this->assertTrue($response->isServerError());
+ }
+
+ public function testHasVary()
+ {
+ $response = new Response();
+ $this->assertFalse($response->hasVary());
+
+ $response->setVary('User-Agent');
+ $this->assertTrue($response->hasVary());
+ }
+
+ public function testSetEtag()
+ {
+ $response = new Response('', 200, array('ETag' => '"12345"'));
+ $response->setEtag();
+
+ $this->assertNull($response->headers->get('Etag'), '->setEtag() removes Etags when call with null');
+ }
+
+ /**
+ * @dataProvider validContentProvider
+ */
+ public function testSetContent($content)
+ {
+ $response = new Response();
+ $response->setContent($content);
+ $this->assertEquals((string) $content, $response->getContent());
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ * @dataProvider invalidContentProvider
+ */
+ public function testSetContentInvalid($content)
+ {
+ $response = new Response();
+ $response->setContent($content);
+ }
+
+ public function testSettersAreChainable()
+ {
+ $response = new Response();
+
+ $setters = array(
+ 'setProtocolVersion' => '1.0',
+ 'setCharset' => 'UTF-8',
+ 'setPublic' => null,
+ 'setPrivate' => null,
+ 'setDate' => $this->createDateTimeNow(),
+ 'expire' => null,
+ 'setMaxAge' => 1,
+ 'setSharedMaxAge' => 1,
+ 'setTtl' => 1,
+ 'setClientTtl' => 1,
+ );
+
+ foreach ($setters as $setter => $arg) {
+ $this->assertEquals($response, $response->{$setter}($arg));
+ }
+ }
+
+ public function testNoDeprecationsAreTriggered()
+ {
+ new DefaultResponse();
+ $this->getMockBuilder(Response::class)->getMock();
+
+ // we just need to ensure that subclasses of Response can be created without any deprecations
+ // being triggered if the subclass does not override any final methods
+ $this->addToAssertionCount(1);
+ }
+
+ public function validContentProvider()
+ {
+ return array(
+ 'obj' => array(new StringableObject()),
+ 'string' => array('Foo'),
+ 'int' => array(2),
+ );
+ }
+
+ public function invalidContentProvider()
+ {
+ return array(
+ 'obj' => array(new \stdClass()),
+ 'array' => array(array()),
+ 'bool' => array(true, '1'),
+ );
+ }
+
+ protected function createDateTimeOneHourAgo()
+ {
+ return $this->createDateTimeNow()->sub(new \DateInterval('PT1H'));
+ }
+
+ protected function createDateTimeOneHourLater()
+ {
+ return $this->createDateTimeNow()->add(new \DateInterval('PT1H'));
+ }
+
+ protected function createDateTimeNow()
+ {
+ $date = new \DateTime();
+
+ return $date->setTimestamp(time());
+ }
+
+ protected function provideResponse()
+ {
+ return new Response();
+ }
+
+ /**
+ * @see http://github.com/zendframework/zend-diactoros for the canonical source repository
+ *
+ * @author Fábio Pacheco
+ * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
+ */
+ public function ianaCodesReasonPhrasesProvider()
+ {
+ if (!in_array('https', stream_get_wrappers(), true)) {
+ $this->markTestSkipped('The "https" wrapper is not available');
+ }
+
+ $ianaHttpStatusCodes = new \DOMDocument();
+
+ libxml_set_streams_context(stream_context_create(array(
+ 'http' => array(
+ 'method' => 'GET',
+ 'timeout' => 30,
+ ),
+ )));
+
+ $ianaHttpStatusCodes->load('https://www.iana.org/assignments/http-status-codes/http-status-codes.xml');
+ if (!$ianaHttpStatusCodes->relaxNGValidate(__DIR__.'/schema/http-status-codes.rng')) {
+ self::fail('Invalid IANA\'s HTTP status code list.');
+ }
+
+ $ianaCodesReasonPhrases = array();
+
+ $xpath = new \DOMXPath($ianaHttpStatusCodes);
+ $xpath->registerNamespace('ns', 'http://www.iana.org/assignments');
+
+ $records = $xpath->query('//ns:record');
+ foreach ($records as $record) {
+ $value = $xpath->query('.//ns:value', $record)->item(0)->nodeValue;
+ $description = $xpath->query('.//ns:description', $record)->item(0)->nodeValue;
+
+ if (in_array($description, array('Unassigned', '(Unused)'), true)) {
+ continue;
+ }
+
+ if (preg_match('/^([0-9]+)\s*\-\s*([0-9]+)$/', $value, $matches)) {
+ for ($value = $matches[1]; $value <= $matches[2]; ++$value) {
+ $ianaCodesReasonPhrases[] = array($value, $description);
+ }
+ } else {
+ $ianaCodesReasonPhrases[] = array($value, $description);
+ }
+ }
+
+ return $ianaCodesReasonPhrases;
+ }
+
+ /**
+ * @dataProvider ianaCodesReasonPhrasesProvider
+ */
+ public function testReasonPhraseDefaultsAgainstIana($code, $reasonPhrase)
+ {
+ $this->assertEquals($reasonPhrase, Response::$statusTexts[$code]);
+ }
+}
+
+class StringableObject
+{
+ public function __toString()
+ {
+ return 'Foo';
+ }
+}
+
+class DefaultResponse extends Response
+{
+}
+
+class ExtendedResponse extends Response
+{
+ public function setLastModified(\DateTime $date = null)
+ {
+ }
+
+ public function getDate()
+ {
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/ResponseTestCase.php b/assets/php/vendor/symfony/http-foundation/Tests/ResponseTestCase.php
new file mode 100644
index 0000000..4ead34c
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/ResponseTestCase.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+
+abstract class ResponseTestCase extends TestCase
+{
+ public function testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE()
+ {
+ // Check for HTTPS and IE 8
+ $request = new Request();
+ $request->server->set('HTTPS', true);
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertFalse($response->headers->has('Cache-Control'));
+
+ // Check for IE 10 and HTTPS
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for IE 9 and HTTPS
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for IE 9 and HTTP
+ $request->server->set('HTTPS', false);
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for IE 8 and HTTP
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for non-IE and HTTPS
+ $request->server->set('HTTPS', true);
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for non-IE and HTTP
+ $request->server->set('HTTPS', false);
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+ }
+
+ abstract protected function provideResponse();
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/ServerBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/ServerBagTest.php
new file mode 100644
index 0000000..f8becec
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/ServerBagTest.php
@@ -0,0 +1,170 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ServerBag;
+
+/**
+ * ServerBagTest.
+ *
+ * @author Bulat Shakirzyanov
+ */
+class ServerBagTest extends TestCase
+{
+ public function testShouldExtractHeadersFromServerArray()
+ {
+ $server = array(
+ 'SOME_SERVER_VARIABLE' => 'value',
+ 'SOME_SERVER_VARIABLE2' => 'value',
+ 'ROOT' => 'value',
+ 'HTTP_CONTENT_TYPE' => 'text/html',
+ 'HTTP_CONTENT_LENGTH' => '0',
+ 'HTTP_ETAG' => 'asdf',
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => 'bar',
+ );
+
+ $bag = new ServerBag($server);
+
+ $this->assertEquals(array(
+ 'CONTENT_TYPE' => 'text/html',
+ 'CONTENT_LENGTH' => '0',
+ 'ETAG' => 'asdf',
+ 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => 'bar',
+ ), $bag->getHeaders());
+ }
+
+ public function testHttpPasswordIsOptional()
+ {
+ $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo'));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => '',
+ ), $bag->getHeaders());
+ }
+
+ public function testHttpBasicAuthWithPhpCgi()
+ {
+ $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar')));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => 'bar',
+ ), $bag->getHeaders());
+ }
+
+ public function testHttpBasicAuthWithPhpCgiBogus()
+ {
+ $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic_'.base64_encode('foo:bar')));
+
+ // Username and passwords should not be set as the header is bogus
+ $headers = $bag->getHeaders();
+ $this->assertArrayNotHasKey('PHP_AUTH_USER', $headers);
+ $this->assertArrayNotHasKey('PHP_AUTH_PW', $headers);
+ }
+
+ public function testHttpBasicAuthWithPhpCgiRedirect()
+ {
+ $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word')));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word'),
+ 'PHP_AUTH_USER' => 'username',
+ 'PHP_AUTH_PW' => 'pass:word',
+ ), $bag->getHeaders());
+ }
+
+ public function testHttpBasicAuthWithPhpCgiEmptyPassword()
+ {
+ $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:')));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => '',
+ ), $bag->getHeaders());
+ }
+
+ public function testHttpDigestAuthWithPhpCgi()
+ {
+ $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+ $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => $digest,
+ 'PHP_AUTH_DIGEST' => $digest,
+ ), $bag->getHeaders());
+ }
+
+ public function testHttpDigestAuthWithPhpCgiBogus()
+ {
+ $digest = 'Digest_username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+ $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest));
+
+ // Username and passwords should not be set as the header is bogus
+ $headers = $bag->getHeaders();
+ $this->assertArrayNotHasKey('PHP_AUTH_USER', $headers);
+ $this->assertArrayNotHasKey('PHP_AUTH_PW', $headers);
+ }
+
+ public function testHttpDigestAuthWithPhpCgiRedirect()
+ {
+ $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+ $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $digest));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => $digest,
+ 'PHP_AUTH_DIGEST' => $digest,
+ ), $bag->getHeaders());
+ }
+
+ public function testOAuthBearerAuth()
+ {
+ $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+ $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $headerContent));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => $headerContent,
+ ), $bag->getHeaders());
+ }
+
+ public function testOAuthBearerAuthWithRedirect()
+ {
+ $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+ $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $headerContent));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => $headerContent,
+ ), $bag->getHeaders());
+ }
+
+ /**
+ * @see https://github.com/symfony/symfony/issues/17345
+ */
+ public function testItDoesNotOverwriteTheAuthorizationHeaderIfItIsAlreadySet()
+ {
+ $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+ $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo', 'HTTP_AUTHORIZATION' => $headerContent));
+
+ $this->assertEquals(array(
+ 'AUTHORIZATION' => $headerContent,
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => '',
+ ), $bag->getHeaders());
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php
new file mode 100644
index 0000000..724a0b9
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php
@@ -0,0 +1,186 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+
+/**
+ * Tests AttributeBag.
+ *
+ * @author Drak
+ */
+class AttributeBagTest extends TestCase
+{
+ private $array = array();
+
+ /**
+ * @var AttributeBag
+ */
+ private $bag;
+
+ protected function setUp()
+ {
+ $this->array = array(
+ 'hello' => 'world',
+ 'always' => 'be happy',
+ 'user.login' => 'drak',
+ 'csrf.token' => array(
+ 'a' => '1234',
+ 'b' => '4321',
+ ),
+ 'category' => array(
+ 'fishing' => array(
+ 'first' => 'cod',
+ 'second' => 'sole',
+ ),
+ ),
+ );
+ $this->bag = new AttributeBag('_sf2');
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->bag = null;
+ $this->array = array();
+ }
+
+ public function testInitialize()
+ {
+ $bag = new AttributeBag();
+ $bag->initialize($this->array);
+ $this->assertEquals($this->array, $bag->all());
+ $array = array('should' => 'change');
+ $bag->initialize($array);
+ $this->assertEquals($array, $bag->all());
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_sf2', $this->bag->getStorageKey());
+ $attributeBag = new AttributeBag('test');
+ $this->assertEquals('test', $attributeBag->getStorageKey());
+ }
+
+ public function testGetSetName()
+ {
+ $this->assertEquals('attributes', $this->bag->getName());
+ $this->bag->setName('foo');
+ $this->assertEquals('foo', $this->bag->getName());
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testHas($key, $value, $exists)
+ {
+ $this->assertEquals($exists, $this->bag->has($key));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testGet($key, $value, $expected)
+ {
+ $this->assertEquals($value, $this->bag->get($key));
+ }
+
+ public function testGetDefaults()
+ {
+ $this->assertNull($this->bag->get('user2.login'));
+ $this->assertEquals('default', $this->bag->get('user2.login', 'default'));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testSet($key, $value, $expected)
+ {
+ $this->bag->set($key, $value);
+ $this->assertEquals($value, $this->bag->get($key));
+ }
+
+ public function testAll()
+ {
+ $this->assertEquals($this->array, $this->bag->all());
+
+ $this->bag->set('hello', 'fabien');
+ $array = $this->array;
+ $array['hello'] = 'fabien';
+ $this->assertEquals($array, $this->bag->all());
+ }
+
+ public function testReplace()
+ {
+ $array = array();
+ $array['name'] = 'jack';
+ $array['foo.bar'] = 'beep';
+ $this->bag->replace($array);
+ $this->assertEquals($array, $this->bag->all());
+ $this->assertNull($this->bag->get('hello'));
+ $this->assertNull($this->bag->get('always'));
+ $this->assertNull($this->bag->get('user.login'));
+ }
+
+ public function testRemove()
+ {
+ $this->assertEquals('world', $this->bag->get('hello'));
+ $this->bag->remove('hello');
+ $this->assertNull($this->bag->get('hello'));
+
+ $this->assertEquals('be happy', $this->bag->get('always'));
+ $this->bag->remove('always');
+ $this->assertNull($this->bag->get('always'));
+
+ $this->assertEquals('drak', $this->bag->get('user.login'));
+ $this->bag->remove('user.login');
+ $this->assertNull($this->bag->get('user.login'));
+ }
+
+ public function testClear()
+ {
+ $this->bag->clear();
+ $this->assertEquals(array(), $this->bag->all());
+ }
+
+ public function attributesProvider()
+ {
+ return array(
+ array('hello', 'world', true),
+ array('always', 'be happy', true),
+ array('user.login', 'drak', true),
+ array('csrf.token', array('a' => '1234', 'b' => '4321'), true),
+ array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true),
+ array('user2.login', null, false),
+ array('never', null, false),
+ array('bye', null, false),
+ array('bye/for/now', null, false),
+ );
+ }
+
+ public function testGetIterator()
+ {
+ $i = 0;
+ foreach ($this->bag as $key => $val) {
+ $this->assertEquals($this->array[$key], $val);
+ ++$i;
+ }
+
+ $this->assertEquals(count($this->array), $i);
+ }
+
+ public function testCount()
+ {
+ $this->assertCount(count($this->array), $this->bag);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php
new file mode 100644
index 0000000..f074ce1
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php
@@ -0,0 +1,182 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;
+
+/**
+ * Tests NamespacedAttributeBag.
+ *
+ * @author Drak
+ */
+class NamespacedAttributeBagTest extends TestCase
+{
+ private $array = array();
+
+ /**
+ * @var NamespacedAttributeBag
+ */
+ private $bag;
+
+ protected function setUp()
+ {
+ $this->array = array(
+ 'hello' => 'world',
+ 'always' => 'be happy',
+ 'user.login' => 'drak',
+ 'csrf.token' => array(
+ 'a' => '1234',
+ 'b' => '4321',
+ ),
+ 'category' => array(
+ 'fishing' => array(
+ 'first' => 'cod',
+ 'second' => 'sole',
+ ),
+ ),
+ );
+ $this->bag = new NamespacedAttributeBag('_sf2', '/');
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->bag = null;
+ $this->array = array();
+ }
+
+ public function testInitialize()
+ {
+ $bag = new NamespacedAttributeBag();
+ $bag->initialize($this->array);
+ $this->assertEquals($this->array, $this->bag->all());
+ $array = array('should' => 'not stick');
+ $bag->initialize($array);
+
+ // should have remained the same
+ $this->assertEquals($this->array, $this->bag->all());
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_sf2', $this->bag->getStorageKey());
+ $attributeBag = new NamespacedAttributeBag('test');
+ $this->assertEquals('test', $attributeBag->getStorageKey());
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testHas($key, $value, $exists)
+ {
+ $this->assertEquals($exists, $this->bag->has($key));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testGet($key, $value, $expected)
+ {
+ $this->assertEquals($value, $this->bag->get($key));
+ }
+
+ public function testGetDefaults()
+ {
+ $this->assertNull($this->bag->get('user2.login'));
+ $this->assertEquals('default', $this->bag->get('user2.login', 'default'));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testSet($key, $value, $expected)
+ {
+ $this->bag->set($key, $value);
+ $this->assertEquals($value, $this->bag->get($key));
+ }
+
+ public function testAll()
+ {
+ $this->assertEquals($this->array, $this->bag->all());
+
+ $this->bag->set('hello', 'fabien');
+ $array = $this->array;
+ $array['hello'] = 'fabien';
+ $this->assertEquals($array, $this->bag->all());
+ }
+
+ public function testReplace()
+ {
+ $array = array();
+ $array['name'] = 'jack';
+ $array['foo.bar'] = 'beep';
+ $this->bag->replace($array);
+ $this->assertEquals($array, $this->bag->all());
+ $this->assertNull($this->bag->get('hello'));
+ $this->assertNull($this->bag->get('always'));
+ $this->assertNull($this->bag->get('user.login'));
+ }
+
+ public function testRemove()
+ {
+ $this->assertEquals('world', $this->bag->get('hello'));
+ $this->bag->remove('hello');
+ $this->assertNull($this->bag->get('hello'));
+
+ $this->assertEquals('be happy', $this->bag->get('always'));
+ $this->bag->remove('always');
+ $this->assertNull($this->bag->get('always'));
+
+ $this->assertEquals('drak', $this->bag->get('user.login'));
+ $this->bag->remove('user.login');
+ $this->assertNull($this->bag->get('user.login'));
+ }
+
+ public function testRemoveExistingNamespacedAttribute()
+ {
+ $this->assertSame('cod', $this->bag->remove('category/fishing/first'));
+ }
+
+ public function testRemoveNonexistingNamespacedAttribute()
+ {
+ $this->assertNull($this->bag->remove('foo/bar/baz'));
+ }
+
+ public function testClear()
+ {
+ $this->bag->clear();
+ $this->assertEquals(array(), $this->bag->all());
+ }
+
+ public function attributesProvider()
+ {
+ return array(
+ array('hello', 'world', true),
+ array('always', 'be happy', true),
+ array('user.login', 'drak', true),
+ array('csrf.token', array('a' => '1234', 'b' => '4321'), true),
+ array('csrf.token/a', '1234', true),
+ array('csrf.token/b', '4321', true),
+ array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true),
+ array('category/fishing', array('first' => 'cod', 'second' => 'sole'), true),
+ array('category/fishing/missing/first', null, false),
+ array('category/fishing/first', 'cod', true),
+ array('category/fishing/second', 'sole', true),
+ array('category/fishing/missing/second', null, false),
+ array('user2.login', null, false),
+ array('never', null, false),
+ array('bye', null, false),
+ array('bye/for/now', null, false),
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
new file mode 100644
index 0000000..fa8626a
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
@@ -0,0 +1,161 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Flash;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag as FlashBag;
+
+/**
+ * AutoExpireFlashBagTest.
+ *
+ * @author Drak
+ */
+class AutoExpireFlashBagTest extends TestCase
+{
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag
+ */
+ private $bag;
+
+ protected $array = array();
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->bag = new FlashBag();
+ $this->array = array('new' => array('notice' => array('A previous flash message')));
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->bag = null;
+ parent::tearDown();
+ }
+
+ public function testInitialize()
+ {
+ $bag = new FlashBag();
+ $array = array('new' => array('notice' => array('A previous flash message')));
+ $bag->initialize($array);
+ $this->assertEquals(array('A previous flash message'), $bag->peek('notice'));
+ $array = array('new' => array(
+ 'notice' => array('Something else'),
+ 'error' => array('a'),
+ ));
+ $bag->initialize($array);
+ $this->assertEquals(array('Something else'), $bag->peek('notice'));
+ $this->assertEquals(array('a'), $bag->peek('error'));
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_symfony_flashes', $this->bag->getStorageKey());
+ $attributeBag = new FlashBag('test');
+ $this->assertEquals('test', $attributeBag->getStorageKey());
+ }
+
+ public function testGetSetName()
+ {
+ $this->assertEquals('flashes', $this->bag->getName());
+ $this->bag->setName('foo');
+ $this->assertEquals('foo', $this->bag->getName());
+ }
+
+ public function testPeek()
+ {
+ $this->assertEquals(array(), $this->bag->peek('non_existing'));
+ $this->assertEquals(array('default'), $this->bag->peek('non_existing', array('default')));
+ $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+ $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+ }
+
+ public function testSet()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+ }
+
+ public function testHas()
+ {
+ $this->assertFalse($this->bag->has('nothing'));
+ $this->assertTrue($this->bag->has('notice'));
+ }
+
+ public function testKeys()
+ {
+ $this->assertEquals(array('notice'), $this->bag->keys());
+ }
+
+ public function testPeekAll()
+ {
+ $array = array(
+ 'new' => array(
+ 'notice' => 'Foo',
+ 'error' => 'Bar',
+ ),
+ );
+
+ $this->bag->initialize($array);
+ $this->assertEquals(array(
+ 'notice' => 'Foo',
+ 'error' => 'Bar',
+ ), $this->bag->peekAll()
+ );
+
+ $this->assertEquals(array(
+ 'notice' => 'Foo',
+ 'error' => 'Bar',
+ ), $this->bag->peekAll()
+ );
+ }
+
+ public function testGet()
+ {
+ $this->assertEquals(array(), $this->bag->get('non_existing'));
+ $this->assertEquals(array('default'), $this->bag->get('non_existing', array('default')));
+ $this->assertEquals(array('A previous flash message'), $this->bag->get('notice'));
+ $this->assertEquals(array(), $this->bag->get('notice'));
+ }
+
+ public function testSetAll()
+ {
+ $this->bag->setAll(array('a' => 'first', 'b' => 'second'));
+ $this->assertFalse($this->bag->has('a'));
+ $this->assertFalse($this->bag->has('b'));
+ }
+
+ public function testAll()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->bag->set('error', 'Bar');
+ $this->assertEquals(array(
+ 'notice' => array('A previous flash message'),
+ ), $this->bag->all()
+ );
+
+ $this->assertEquals(array(), $this->bag->all());
+ }
+
+ public function testClear()
+ {
+ $this->assertEquals(array('notice' => array('A previous flash message')), $this->bag->clear());
+ }
+
+ public function testDoNotRemoveTheNewFlashesWhenDisplayingTheExistingOnes()
+ {
+ $this->bag->add('success', 'Something');
+ $this->bag->all();
+
+ $this->assertEquals(array('new' => array('success' => array('Something')), 'display' => array()), $this->array);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php
new file mode 100644
index 0000000..c4e75b1
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php
@@ -0,0 +1,132 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Flash;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+
+/**
+ * FlashBagTest.
+ *
+ * @author Drak
+ */
+class FlashBagTest extends TestCase
+{
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface
+ */
+ private $bag;
+
+ protected $array = array();
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->bag = new FlashBag();
+ $this->array = array('notice' => array('A previous flash message'));
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->bag = null;
+ parent::tearDown();
+ }
+
+ public function testInitialize()
+ {
+ $bag = new FlashBag();
+ $bag->initialize($this->array);
+ $this->assertEquals($this->array, $bag->peekAll());
+ $array = array('should' => array('change'));
+ $bag->initialize($array);
+ $this->assertEquals($array, $bag->peekAll());
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_symfony_flashes', $this->bag->getStorageKey());
+ $attributeBag = new FlashBag('test');
+ $this->assertEquals('test', $attributeBag->getStorageKey());
+ }
+
+ public function testGetSetName()
+ {
+ $this->assertEquals('flashes', $this->bag->getName());
+ $this->bag->setName('foo');
+ $this->assertEquals('foo', $this->bag->getName());
+ }
+
+ public function testPeek()
+ {
+ $this->assertEquals(array(), $this->bag->peek('non_existing'));
+ $this->assertEquals(array('default'), $this->bag->peek('not_existing', array('default')));
+ $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+ $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice'));
+ }
+
+ public function testGet()
+ {
+ $this->assertEquals(array(), $this->bag->get('non_existing'));
+ $this->assertEquals(array('default'), $this->bag->get('not_existing', array('default')));
+ $this->assertEquals(array('A previous flash message'), $this->bag->get('notice'));
+ $this->assertEquals(array(), $this->bag->get('notice'));
+ }
+
+ public function testAll()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->bag->set('error', 'Bar');
+ $this->assertEquals(array(
+ 'notice' => array('Foo'),
+ 'error' => array('Bar'), ), $this->bag->all()
+ );
+
+ $this->assertEquals(array(), $this->bag->all());
+ }
+
+ public function testSet()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->bag->set('notice', 'Bar');
+ $this->assertEquals(array('Bar'), $this->bag->peek('notice'));
+ }
+
+ public function testHas()
+ {
+ $this->assertFalse($this->bag->has('nothing'));
+ $this->assertTrue($this->bag->has('notice'));
+ }
+
+ public function testKeys()
+ {
+ $this->assertEquals(array('notice'), $this->bag->keys());
+ }
+
+ public function testPeekAll()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->bag->set('error', 'Bar');
+ $this->assertEquals(array(
+ 'notice' => array('Foo'),
+ 'error' => array('Bar'),
+ ), $this->bag->peekAll()
+ );
+ $this->assertTrue($this->bag->has('notice'));
+ $this->assertTrue($this->bag->has('error'));
+ $this->assertEquals(array(
+ 'notice' => array('Foo'),
+ 'error' => array('Bar'),
+ ), $this->bag->peekAll()
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/SessionTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/SessionTest.php
new file mode 100644
index 0000000..41720e4
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/SessionTest.php
@@ -0,0 +1,242 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+
+/**
+ * SessionTest.
+ *
+ * @author Fabien Potencier
+ * @author Robert Schönthal
+ * @author Drak
+ */
+class SessionTest extends TestCase
+{
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface
+ */
+ protected $storage;
+
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+ */
+ protected $session;
+
+ protected function setUp()
+ {
+ $this->storage = new MockArraySessionStorage();
+ $this->session = new Session($this->storage, new AttributeBag(), new FlashBag());
+ }
+
+ protected function tearDown()
+ {
+ $this->storage = null;
+ $this->session = null;
+ }
+
+ public function testStart()
+ {
+ $this->assertEquals('', $this->session->getId());
+ $this->assertTrue($this->session->start());
+ $this->assertNotEquals('', $this->session->getId());
+ }
+
+ public function testIsStarted()
+ {
+ $this->assertFalse($this->session->isStarted());
+ $this->session->start();
+ $this->assertTrue($this->session->isStarted());
+ }
+
+ public function testSetId()
+ {
+ $this->assertEquals('', $this->session->getId());
+ $this->session->setId('0123456789abcdef');
+ $this->session->start();
+ $this->assertEquals('0123456789abcdef', $this->session->getId());
+ }
+
+ public function testSetName()
+ {
+ $this->assertEquals('MOCKSESSID', $this->session->getName());
+ $this->session->setName('session.test.com');
+ $this->session->start();
+ $this->assertEquals('session.test.com', $this->session->getName());
+ }
+
+ public function testGet()
+ {
+ // tests defaults
+ $this->assertNull($this->session->get('foo'));
+ $this->assertEquals(1, $this->session->get('foo', 1));
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testSet($key, $value)
+ {
+ $this->session->set($key, $value);
+ $this->assertEquals($value, $this->session->get($key));
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testHas($key, $value)
+ {
+ $this->session->set($key, $value);
+ $this->assertTrue($this->session->has($key));
+ $this->assertFalse($this->session->has($key.'non_value'));
+ }
+
+ public function testReplace()
+ {
+ $this->session->replace(array('happiness' => 'be good', 'symfony' => 'awesome'));
+ $this->assertEquals(array('happiness' => 'be good', 'symfony' => 'awesome'), $this->session->all());
+ $this->session->replace(array());
+ $this->assertEquals(array(), $this->session->all());
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testAll($key, $value, $result)
+ {
+ $this->session->set($key, $value);
+ $this->assertEquals($result, $this->session->all());
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testClear($key, $value)
+ {
+ $this->session->set('hi', 'fabien');
+ $this->session->set($key, $value);
+ $this->session->clear();
+ $this->assertEquals(array(), $this->session->all());
+ }
+
+ public function setProvider()
+ {
+ return array(
+ array('foo', 'bar', array('foo' => 'bar')),
+ array('foo.bar', 'too much beer', array('foo.bar' => 'too much beer')),
+ array('great', 'symfony is great', array('great' => 'symfony is great')),
+ );
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testRemove($key, $value)
+ {
+ $this->session->set('hi.world', 'have a nice day');
+ $this->session->set($key, $value);
+ $this->session->remove($key);
+ $this->assertEquals(array('hi.world' => 'have a nice day'), $this->session->all());
+ }
+
+ public function testInvalidate()
+ {
+ $this->session->set('invalidate', 123);
+ $this->session->invalidate();
+ $this->assertEquals(array(), $this->session->all());
+ }
+
+ public function testMigrate()
+ {
+ $this->session->set('migrate', 321);
+ $this->session->migrate();
+ $this->assertEquals(321, $this->session->get('migrate'));
+ }
+
+ public function testMigrateDestroy()
+ {
+ $this->session->set('migrate', 333);
+ $this->session->migrate(true);
+ $this->assertEquals(333, $this->session->get('migrate'));
+ }
+
+ public function testSave()
+ {
+ $this->session->start();
+ $this->session->save();
+
+ $this->assertFalse($this->session->isStarted());
+ }
+
+ public function testGetId()
+ {
+ $this->assertEquals('', $this->session->getId());
+ $this->session->start();
+ $this->assertNotEquals('', $this->session->getId());
+ }
+
+ public function testGetFlashBag()
+ {
+ $this->assertInstanceOf('Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface', $this->session->getFlashBag());
+ }
+
+ public function testGetIterator()
+ {
+ $attributes = array('hello' => 'world', 'symfony' => 'rocks');
+ foreach ($attributes as $key => $val) {
+ $this->session->set($key, $val);
+ }
+
+ $i = 0;
+ foreach ($this->session as $key => $val) {
+ $this->assertEquals($attributes[$key], $val);
+ ++$i;
+ }
+
+ $this->assertEquals(count($attributes), $i);
+ }
+
+ public function testGetCount()
+ {
+ $this->session->set('hello', 'world');
+ $this->session->set('symfony', 'rocks');
+
+ $this->assertCount(2, $this->session);
+ }
+
+ public function testGetMeta()
+ {
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag());
+ }
+
+ public function testIsEmpty()
+ {
+ $this->assertTrue($this->session->isEmpty());
+
+ $this->session->set('hello', 'world');
+ $this->assertFalse($this->session->isEmpty());
+
+ $this->session->remove('hello');
+ $this->assertTrue($this->session->isEmpty());
+
+ $flash = $this->session->getFlashBag();
+ $flash->set('hello', 'world');
+ $this->assertFalse($this->session->isEmpty());
+
+ $flash->get('hello');
+ $this->assertTrue($this->session->isEmpty());
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
new file mode 100644
index 0000000..3ac081e
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @requires PHP 7.0
+ */
+class AbstractSessionHandlerTest extends TestCase
+{
+ private static $server;
+
+ public static function setUpBeforeClass()
+ {
+ $spec = array(
+ 1 => array('file', '/dev/null', 'w'),
+ 2 => array('file', '/dev/null', 'w'),
+ );
+ if (!self::$server = @proc_open('exec php -S localhost:8053', $spec, $pipes, __DIR__.'/Fixtures')) {
+ self::markTestSkipped('PHP server unable to start.');
+ }
+ sleep(1);
+ }
+
+ public static function tearDownAfterClass()
+ {
+ if (self::$server) {
+ proc_terminate(self::$server);
+ proc_close(self::$server);
+ }
+ }
+
+ /**
+ * @dataProvider provideSession
+ */
+ public function testSession($fixture)
+ {
+ $context = array('http' => array('header' => "Cookie: sid=123abc\r\n"));
+ $context = stream_context_create($context);
+ $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
+
+ $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result);
+ }
+
+ public function provideSession()
+ {
+ foreach (glob(__DIR__.'/Fixtures/*.php') as $file) {
+ yield array(pathinfo($file, PATHINFO_FILENAME));
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc
new file mode 100644
index 0000000..7a064c7
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc
@@ -0,0 +1,151 @@
+data = $data;
+ }
+
+ public function open($path, $name)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::open($path, $name);
+ }
+
+ public function validateId($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::validateId($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::read($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::destroy($sessionId);
+ }
+
+ public function close()
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ public function gc($maxLifetime)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ protected function doRead($sessionId)
+ {
+ echo __FUNCTION__.': ', $this->data, "\n";
+
+ return $this->data;
+ }
+
+ protected function doWrite($sessionId, $data)
+ {
+ echo __FUNCTION__.': ', $data, "\n";
+
+ return true;
+ }
+
+ protected function doDestroy($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected
new file mode 100644
index 0000000..8203714
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected
@@ -0,0 +1,17 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+write
+destroy
+doDestroy
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+ [2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
+)
+shutdown
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php
new file mode 100644
index 0000000..3cfc125
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php
@@ -0,0 +1,8 @@
+ Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+)
+shutdown
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php
new file mode 100644
index 0000000..3e62fb9
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php
@@ -0,0 +1,8 @@
+ Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+ [2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly
+)
+shutdown
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php
new file mode 100644
index 0000000..a0f635c
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php
@@ -0,0 +1,10 @@
+ bar
+)
+$_SESSION is not empty
+write
+destroy
+close
+$_SESSION is not empty
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=0, private, must-revalidate
+)
+shutdown
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php
new file mode 100644
index 0000000..96dca3c
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php
@@ -0,0 +1,24 @@
+setSaveHandler(new TestSessionHandler());
+$flash = new FlashBag();
+$storage->registerBag($flash);
+$storage->start();
+
+$flash->add('foo', 'bar');
+
+print_r($flash->get('foo'));
+echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
+echo "\n";
+
+$storage->save();
+
+echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
+
+ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
new file mode 100644
index 0000000..33da0a5
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
@@ -0,0 +1,15 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+updateTimestamp
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+ [2] => Set-Cookie: abc=def
+)
+shutdown
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
new file mode 100644
index 0000000..ffb5b20
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
@@ -0,0 +1,8 @@
+ Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+ [2] => Set-Cookie: abc=def
+)
+shutdown
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php
new file mode 100644
index 0000000..ec51193
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php
@@ -0,0 +1,13 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcacheSessionHandler;
+
+/**
+ * @requires extension memcache
+ * @group time-sensitive
+ * @group legacy
+ */
+class MemcacheSessionHandlerTest extends TestCase
+{
+ const PREFIX = 'prefix_';
+ const TTL = 1000;
+
+ /**
+ * @var MemcacheSessionHandler
+ */
+ protected $storage;
+
+ protected $memcache;
+
+ protected function setUp()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcache class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+ }
+
+ parent::setUp();
+ $this->memcache = $this->getMockBuilder('Memcache')->getMock();
+ $this->storage = new MemcacheSessionHandler(
+ $this->memcache,
+ array('prefix' => self::PREFIX, 'expiretime' => self::TTL)
+ );
+ }
+
+ protected function tearDown()
+ {
+ $this->memcache = null;
+ $this->storage = null;
+ parent::tearDown();
+ }
+
+ public function testOpenSession()
+ {
+ $this->assertTrue($this->storage->open('', ''));
+ }
+
+ public function testCloseSession()
+ {
+ $this->assertTrue($this->storage->close());
+ }
+
+ public function testReadSession()
+ {
+ $this->memcache
+ ->expects($this->once())
+ ->method('get')
+ ->with(self::PREFIX.'id')
+ ;
+
+ $this->assertEquals('', $this->storage->read('id'));
+ }
+
+ public function testWriteSession()
+ {
+ $this->memcache
+ ->expects($this->once())
+ ->method('set')
+ ->with(self::PREFIX.'id', 'data', 0, $this->equalTo(time() + self::TTL, 2))
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->write('id', 'data'));
+ }
+
+ public function testDestroySession()
+ {
+ $this->memcache
+ ->expects($this->once())
+ ->method('delete')
+ ->with(self::PREFIX.'id')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->destroy('id'));
+ }
+
+ public function testGcSession()
+ {
+ $this->assertTrue($this->storage->gc(123));
+ }
+
+ /**
+ * @dataProvider getOptionFixtures
+ */
+ public function testSupportedOptions($options, $supported)
+ {
+ try {
+ new MemcacheSessionHandler($this->memcache, $options);
+ $this->assertTrue($supported);
+ } catch (\InvalidArgumentException $e) {
+ $this->assertFalse($supported);
+ }
+ }
+
+ public function getOptionFixtures()
+ {
+ return array(
+ array(array('prefix' => 'session'), true),
+ array(array('expiretime' => 100), true),
+ array(array('prefix' => 'session', 'expiretime' => 200), true),
+ array(array('expiretime' => 100, 'foo' => 'bar'), false),
+ );
+ }
+
+ public function testGetConnection()
+ {
+ $method = new \ReflectionMethod($this->storage, 'getMemcache');
+ $method->setAccessible(true);
+
+ $this->assertInstanceOf('\Memcache', $method->invoke($this->storage));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
new file mode 100644
index 0000000..2e7be35
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
@@ -0,0 +1,139 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler;
+
+/**
+ * @requires extension memcached
+ * @group time-sensitive
+ */
+class MemcachedSessionHandlerTest extends TestCase
+{
+ const PREFIX = 'prefix_';
+ const TTL = 1000;
+
+ /**
+ * @var MemcachedSessionHandler
+ */
+ protected $storage;
+
+ protected $memcached;
+
+ protected function setUp()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcached class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+ }
+
+ parent::setUp();
+
+ if (version_compare(phpversion('memcached'), '2.2.0', '>=') && version_compare(phpversion('memcached'), '3.0.0b1', '<')) {
+ $this->markTestSkipped('Tests can only be run with memcached extension 2.1.0 or lower, or 3.0.0b1 or higher');
+ }
+
+ $this->memcached = $this->getMockBuilder('Memcached')->getMock();
+ $this->storage = new MemcachedSessionHandler(
+ $this->memcached,
+ array('prefix' => self::PREFIX, 'expiretime' => self::TTL)
+ );
+ }
+
+ protected function tearDown()
+ {
+ $this->memcached = null;
+ $this->storage = null;
+ parent::tearDown();
+ }
+
+ public function testOpenSession()
+ {
+ $this->assertTrue($this->storage->open('', ''));
+ }
+
+ public function testCloseSession()
+ {
+ $this->assertTrue($this->storage->close());
+ }
+
+ public function testReadSession()
+ {
+ $this->memcached
+ ->expects($this->once())
+ ->method('get')
+ ->with(self::PREFIX.'id')
+ ;
+
+ $this->assertEquals('', $this->storage->read('id'));
+ }
+
+ public function testWriteSession()
+ {
+ $this->memcached
+ ->expects($this->once())
+ ->method('set')
+ ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL, 2))
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->write('id', 'data'));
+ }
+
+ public function testDestroySession()
+ {
+ $this->memcached
+ ->expects($this->once())
+ ->method('delete')
+ ->with(self::PREFIX.'id')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->destroy('id'));
+ }
+
+ public function testGcSession()
+ {
+ $this->assertTrue($this->storage->gc(123));
+ }
+
+ /**
+ * @dataProvider getOptionFixtures
+ */
+ public function testSupportedOptions($options, $supported)
+ {
+ try {
+ new MemcachedSessionHandler($this->memcached, $options);
+ $this->assertTrue($supported);
+ } catch (\InvalidArgumentException $e) {
+ $this->assertFalse($supported);
+ }
+ }
+
+ public function getOptionFixtures()
+ {
+ return array(
+ array(array('prefix' => 'session'), true),
+ array(array('expiretime' => 100), true),
+ array(array('prefix' => 'session', 'expiretime' => 200), true),
+ array(array('expiretime' => 100, 'foo' => 'bar'), false),
+ );
+ }
+
+ public function testGetConnection()
+ {
+ $method = new \ReflectionMethod($this->storage, 'getMemcached');
+ $method->setAccessible(true);
+
+ $this->assertInstanceOf('\Memcached', $method->invoke($this->storage));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
new file mode 100644
index 0000000..da05109
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
@@ -0,0 +1,333 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
+
+/**
+ * @author Markus Bachmann
+ * @group time-sensitive
+ * @group legacy
+ */
+class MongoDbSessionHandlerTest extends TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $mongo;
+ private $storage;
+ public $options;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ if (extension_loaded('mongodb')) {
+ if (!class_exists('MongoDB\Client')) {
+ $this->markTestSkipped('The mongodb/mongodb package is required.');
+ }
+ } elseif (!extension_loaded('mongo')) {
+ $this->markTestSkipped('The Mongo or MongoDB extension is required.');
+ }
+
+ if (phpversion('mongodb')) {
+ $mongoClass = 'MongoDB\Client';
+ } else {
+ $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
+ }
+
+ $this->mongo = $this->getMockBuilder($mongoClass)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->options = array(
+ 'id_field' => '_id',
+ 'data_field' => 'data',
+ 'time_field' => 'time',
+ 'expiry_field' => 'expires_at',
+ 'database' => 'sf2-test',
+ 'collection' => 'session-test',
+ );
+
+ $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructorShouldThrowExceptionForInvalidMongo()
+ {
+ new MongoDbSessionHandler(new \stdClass(), $this->options);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructorShouldThrowExceptionForMissingOptions()
+ {
+ new MongoDbSessionHandler($this->mongo, array());
+ }
+
+ public function testOpenMethodAlwaysReturnTrue()
+ {
+ $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true');
+ }
+
+ public function testCloseMethodAlwaysReturnTrue()
+ {
+ $this->assertTrue($this->storage->close(), 'The "close" method should always return true');
+ }
+
+ public function testRead()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ // defining the timeout before the actual method call
+ // allows to test for "greater than" values in the $criteria
+ $testTimeout = time() + 1;
+
+ $collection->expects($this->once())
+ ->method('findOne')
+ ->will($this->returnCallback(function ($criteria) use ($testTimeout) {
+ $this->assertArrayHasKey($this->options['id_field'], $criteria);
+ $this->assertEquals($criteria[$this->options['id_field']], 'foo');
+
+ $this->assertArrayHasKey($this->options['expiry_field'], $criteria);
+ $this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]);
+
+ if (phpversion('mongodb')) {
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$gte']);
+ $this->assertGreaterThanOrEqual(round((string) $criteria[$this->options['expiry_field']]['$gte'] / 1000), $testTimeout);
+ } else {
+ $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']);
+ $this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout);
+ }
+
+ $fields = array(
+ $this->options['id_field'] => 'foo',
+ );
+
+ if (phpversion('mongodb')) {
+ $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
+ $fields[$this->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000);
+ } else {
+ $fields[$this->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY);
+ $fields[$this->options['id_field']] = new \MongoDate();
+ }
+
+ return $fields;
+ }));
+
+ $this->assertEquals('bar', $this->storage->read('foo'));
+ }
+
+ public function testWrite()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $data = array();
+
+ $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+ $collection->expects($this->once())
+ ->method($methodName)
+ ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+ $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria);
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals(array('upsert' => true), $options);
+ } else {
+ $this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
+ }
+
+ $data = $updateData['$set'];
+ }));
+
+ $expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime');
+ $this->assertTrue($this->storage->write('foo', 'bar'));
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
+ $this->assertGreaterThanOrEqual($expectedExpiry, round((string) $data[$this->options['expiry_field']] / 1000));
+ } else {
+ $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
+ $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
+ $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
+ $this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
+ }
+ }
+
+ public function testWriteWhenUsingExpiresField()
+ {
+ $this->options = array(
+ 'id_field' => '_id',
+ 'data_field' => 'data',
+ 'time_field' => 'time',
+ 'database' => 'sf2-test',
+ 'collection' => 'session-test',
+ 'expiry_field' => 'expiresAt',
+ );
+
+ $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
+
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $data = array();
+
+ $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+ $collection->expects($this->once())
+ ->method($methodName)
+ ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+ $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria);
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals(array('upsert' => true), $options);
+ } else {
+ $this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
+ }
+
+ $data = $updateData['$set'];
+ }));
+
+ $this->assertTrue($this->storage->write('foo', 'bar'));
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
+ } else {
+ $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
+ $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
+ $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
+ }
+ }
+
+ public function testReplaceSessionData()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $data = array();
+
+ $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+ $collection->expects($this->exactly(2))
+ ->method($methodName)
+ ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+ $data = $updateData;
+ }));
+
+ $this->storage->write('foo', 'bar');
+ $this->storage->write('foo', 'foobar');
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
+ } else {
+ $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
+ }
+ }
+
+ public function testDestroy()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
+
+ $collection->expects($this->once())
+ ->method($methodName)
+ ->with(array($this->options['id_field'] => 'foo'));
+
+ $this->assertTrue($this->storage->destroy('foo'));
+ }
+
+ public function testGc()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $methodName = phpversion('mongodb') ? 'deleteMany' : 'remove';
+
+ $collection->expects($this->once())
+ ->method($methodName)
+ ->will($this->returnCallback(function ($criteria) {
+ if (phpversion('mongodb')) {
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$lt']);
+ $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000));
+ } else {
+ $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']);
+ $this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec);
+ }
+ }));
+
+ $this->assertTrue($this->storage->gc(1));
+ }
+
+ public function testGetConnection()
+ {
+ $method = new \ReflectionMethod($this->storage, 'getMongo');
+ $method->setAccessible(true);
+
+ if (phpversion('mongodb')) {
+ $mongoClass = 'MongoDB\Client';
+ } else {
+ $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
+ }
+
+ $this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
+ }
+
+ private function createMongoCollectionMock()
+ {
+ $collectionClass = 'MongoCollection';
+ if (phpversion('mongodb')) {
+ $collectionClass = 'MongoDB\Collection';
+ }
+
+ $collection = $this->getMockBuilder($collectionClass)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $collection;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
new file mode 100644
index 0000000..a6264e5
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+/**
+ * Test class for NativeFileSessionHandler.
+ *
+ * @author Drak
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NativeFileSessionHandlerTest extends TestCase
+{
+ public function testConstruct()
+ {
+ $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler(sys_get_temp_dir()));
+
+ $this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName());
+ $this->assertEquals('user', ini_get('session.save_handler'));
+
+ $this->assertEquals(sys_get_temp_dir(), ini_get('session.save_path'));
+ $this->assertEquals('TESTING', ini_get('session.name'));
+ }
+
+ /**
+ * @dataProvider savePathDataProvider
+ */
+ public function testConstructSavePath($savePath, $expectedSavePath, $path)
+ {
+ $handler = new NativeFileSessionHandler($savePath);
+ $this->assertEquals($expectedSavePath, ini_get('session.save_path'));
+ $this->assertTrue(is_dir(realpath($path)));
+
+ rmdir($path);
+ }
+
+ public function savePathDataProvider()
+ {
+ $base = sys_get_temp_dir();
+
+ return array(
+ array("$base/foo", "$base/foo", "$base/foo"),
+ array("5;$base/foo", "5;$base/foo", "$base/foo"),
+ array("5;0600;$base/foo", "5;0600;$base/foo", "$base/foo"),
+ );
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructException()
+ {
+ $handler = new NativeFileSessionHandler('something;invalid;with;too-many-args');
+ }
+
+ public function testConstructDefault()
+ {
+ $path = ini_get('session.save_path');
+ $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler());
+
+ $this->assertEquals($path, ini_get('session.save_path'));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php
new file mode 100644
index 0000000..4a9fb60
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
+
+/**
+ * Test class for NativeSessionHandler.
+ *
+ * @author Drak
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ * @group legacy
+ */
+class NativeSessionHandlerTest extends TestCase
+{
+ /**
+ * @expectedDeprecation The Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.
+ */
+ public function testConstruct()
+ {
+ $handler = new NativeSessionHandler();
+
+ $this->assertInstanceOf('SessionHandler', $handler);
+ $this->assertTrue($handler instanceof NativeSessionHandler);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php
new file mode 100644
index 0000000..718fd0f
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Session;
+
+/**
+ * Test class for NullSessionHandler.
+ *
+ * @author Drak
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NullSessionHandlerTest extends TestCase
+{
+ public function testSaveHandlers()
+ {
+ $storage = $this->getStorage();
+ $this->assertEquals('user', ini_get('session.save_handler'));
+ }
+
+ public function testSession()
+ {
+ session_id('nullsessionstorage');
+ $storage = $this->getStorage();
+ $session = new Session($storage);
+ $this->assertNull($session->get('something'));
+ $session->set('something', 'unique');
+ $this->assertEquals('unique', $session->get('something'));
+ }
+
+ public function testNothingIsPersisted()
+ {
+ session_id('nullsessionstorage');
+ $storage = $this->getStorage();
+ $session = new Session($storage);
+ $session->start();
+ $this->assertEquals('nullsessionstorage', $session->getId());
+ $this->assertNull($session->get('something'));
+ }
+
+ public function getStorage()
+ {
+ return new NativeSessionStorage(array(), new NullSessionHandler());
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
new file mode 100644
index 0000000..0a0e449
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
@@ -0,0 +1,411 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
+
+/**
+ * @requires extension pdo_sqlite
+ * @group time-sensitive
+ */
+class PdoSessionHandlerTest extends TestCase
+{
+ private $dbFile;
+
+ protected function tearDown()
+ {
+ // make sure the temporary database file is deleted when it has been created (even when a test fails)
+ if ($this->dbFile) {
+ @unlink($this->dbFile);
+ }
+ parent::tearDown();
+ }
+
+ protected function getPersistentSqliteDsn()
+ {
+ $this->dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
+
+ return 'sqlite:'.$this->dbFile;
+ }
+
+ protected function getMemorySqlitePdo()
+ {
+ $pdo = new \PDO('sqlite::memory:');
+ $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ $storage = new PdoSessionHandler($pdo);
+ $storage->createTable();
+
+ return $pdo;
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testWrongPdoErrMode()
+ {
+ $pdo = $this->getMemorySqlitePdo();
+ $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
+
+ $storage = new PdoSessionHandler($pdo);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testInexistentTable()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo(), array('db_table' => 'inexistent_table'));
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testCreateTableTwice()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->createTable();
+ }
+
+ public function testWithLazyDsnConnection()
+ {
+ $dsn = $this->getPersistentSqliteDsn();
+
+ $storage = new PdoSessionHandler($dsn);
+ $storage->createTable();
+ $storage->open('', 'sid');
+ $data = $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+ $this->assertSame('', $data, 'New session returns empty string data');
+
+ $storage->open('', 'sid');
+ $data = $storage->read('id');
+ $storage->close();
+ $this->assertSame('data', $data, 'Written value can be read back correctly');
+ }
+
+ public function testWithLazySavePathConnection()
+ {
+ $dsn = $this->getPersistentSqliteDsn();
+
+ // Open is called with what ini_set('session.save_path', $dsn) would mean
+ $storage = new PdoSessionHandler(null);
+ $storage->open($dsn, 'sid');
+ $storage->createTable();
+ $data = $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+ $this->assertSame('', $data, 'New session returns empty string data');
+
+ $storage->open($dsn, 'sid');
+ $data = $storage->read('id');
+ $storage->close();
+ $this->assertSame('data', $data, 'Written value can be read back correctly');
+ }
+
+ public function testReadWriteReadWithNullByte()
+ {
+ $sessionData = 'da'."\0".'ta';
+
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->open('', 'sid');
+ $readData = $storage->read('id');
+ $storage->write('id', $sessionData);
+ $storage->close();
+ $this->assertSame('', $readData, 'New session returns empty string data');
+
+ $storage->open('', 'sid');
+ $readData = $storage->read('id');
+ $storage->close();
+ $this->assertSame($sessionData, $readData, 'Written value can be read back correctly');
+ }
+
+ public function testReadConvertsStreamToString()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+ }
+
+ $pdo = new MockPdo('pgsql');
+ $pdo->prepareResult = $this->getMockBuilder('PDOStatement')->getMock();
+
+ $content = 'foobar';
+ $stream = $this->createStream($content);
+
+ $pdo->prepareResult->expects($this->once())->method('fetchAll')
+ ->will($this->returnValue(array(array($stream, 42, time()))));
+
+ $storage = new PdoSessionHandler($pdo);
+ $result = $storage->read('foo');
+
+ $this->assertSame($content, $result);
+ }
+
+ public function testReadLockedConvertsStreamToString()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+ }
+ if (ini_get('session.use_strict_mode')) {
+ $this->markTestSkipped('Strict mode needs no locking for new sessions.');
+ }
+
+ $pdo = new MockPdo('pgsql');
+ $selectStmt = $this->getMockBuilder('PDOStatement')->getMock();
+ $insertStmt = $this->getMockBuilder('PDOStatement')->getMock();
+
+ $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) {
+ return 0 === strpos($statement, 'INSERT') ? $insertStmt : $selectStmt;
+ };
+
+ $content = 'foobar';
+ $stream = $this->createStream($content);
+ $exception = null;
+
+ $selectStmt->expects($this->atLeast(2))->method('fetchAll')
+ ->will($this->returnCallback(function () use (&$exception, $stream) {
+ return $exception ? array(array($stream, 42, time())) : array();
+ }));
+
+ $insertStmt->expects($this->once())->method('execute')
+ ->will($this->returnCallback(function () use (&$exception) {
+ throw $exception = new \PDOException('', '23');
+ }));
+
+ $storage = new PdoSessionHandler($pdo);
+ $result = $storage->read('foo');
+
+ $this->assertSame($content, $result);
+ }
+
+ public function testReadingRequiresExactlySameId()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->open('', 'sid');
+ $storage->write('id', 'data');
+ $storage->write('test', 'data');
+ $storage->write('space ', 'data');
+ $storage->close();
+
+ $storage->open('', 'sid');
+ $readDataCaseSensitive = $storage->read('ID');
+ $readDataNoCharFolding = $storage->read('tést');
+ $readDataKeepSpace = $storage->read('space ');
+ $readDataExtraSpace = $storage->read('space ');
+ $storage->close();
+
+ $this->assertSame('', $readDataCaseSensitive, 'Retrieval by ID should be case-sensitive (collation setting)');
+ $this->assertSame('', $readDataNoCharFolding, 'Retrieval by ID should not do character folding (collation setting)');
+ $this->assertSame('data', $readDataKeepSpace, 'Retrieval by ID requires spaces as-is');
+ $this->assertSame('', $readDataExtraSpace, 'Retrieval by ID requires spaces as-is');
+ }
+
+ /**
+ * Simulates session_regenerate_id(true) which will require an INSERT or UPDATE (replace).
+ */
+ public function testWriteDifferentSessionIdThanRead()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->destroy('id');
+ $storage->write('new_id', 'data_of_new_session_id');
+ $storage->close();
+
+ $storage->open('', 'sid');
+ $data = $storage->read('new_id');
+ $storage->close();
+
+ $this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available');
+ }
+
+ public function testWrongUsageStillWorks()
+ {
+ // wrong method sequence that should no happen, but still works
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->write('id', 'data');
+ $storage->write('other_id', 'other_data');
+ $storage->destroy('inexistent');
+ $storage->open('', 'sid');
+ $data = $storage->read('id');
+ $otherData = $storage->read('other_id');
+ $storage->close();
+
+ $this->assertSame('data', $data);
+ $this->assertSame('other_data', $otherData);
+ }
+
+ public function testSessionDestroy()
+ {
+ $pdo = $this->getMemorySqlitePdo();
+ $storage = new PdoSessionHandler($pdo);
+
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+ $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
+
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->destroy('id');
+ $storage->close();
+ $this->assertEquals(0, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
+
+ $storage->open('', 'sid');
+ $data = $storage->read('id');
+ $storage->close();
+ $this->assertSame('', $data, 'Destroyed session returns empty string');
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testSessionGC()
+ {
+ $previousLifeTime = ini_set('session.gc_maxlifetime', 1000);
+ $pdo = $this->getMemorySqlitePdo();
+ $storage = new PdoSessionHandler($pdo);
+
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+
+ $storage->open('', 'sid');
+ $storage->read('gc_id');
+ ini_set('session.gc_maxlifetime', -1); // test that you can set lifetime of a session after it has been read
+ $storage->write('gc_id', 'data');
+ $storage->close();
+ $this->assertEquals(2, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called');
+
+ $storage->open('', 'sid');
+ $data = $storage->read('gc_id');
+ $storage->gc(-1);
+ $storage->close();
+
+ ini_set('session.gc_maxlifetime', $previousLifeTime);
+
+ $this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet');
+ $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned');
+ }
+
+ public function testGetConnection()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+
+ $method = new \ReflectionMethod($storage, 'getConnection');
+ $method->setAccessible(true);
+
+ $this->assertInstanceOf('\PDO', $method->invoke($storage));
+ }
+
+ public function testGetConnectionConnectsIfNeeded()
+ {
+ $storage = new PdoSessionHandler('sqlite::memory:');
+
+ $method = new \ReflectionMethod($storage, 'getConnection');
+ $method->setAccessible(true);
+
+ $this->assertInstanceOf('\PDO', $method->invoke($storage));
+ }
+
+ /**
+ * @dataProvider provideUrlDsnPairs
+ */
+ public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null)
+ {
+ $storage = new PdoSessionHandler($url);
+
+ $this->assertAttributeEquals($expectedDsn, 'dsn', $storage);
+
+ if (null !== $expectedUser) {
+ $this->assertAttributeEquals($expectedUser, 'username', $storage);
+ }
+
+ if (null !== $expectedPassword) {
+ $this->assertAttributeEquals($expectedPassword, 'password', $storage);
+ }
+ }
+
+ public function provideUrlDsnPairs()
+ {
+ yield array('mysql://localhost/test', 'mysql:host=localhost;dbname=test;');
+ yield array('mysql://localhost:56/test', 'mysql:host=localhost;port=56;dbname=test;');
+ yield array('mysql2://root:pwd@localhost/test', 'mysql:host=localhost;dbname=test;', 'root', 'pwd');
+ yield array('postgres://localhost/test', 'pgsql:host=localhost;dbname=test;');
+ yield array('postgresql://localhost:5634/test', 'pgsql:host=localhost;port=5634;dbname=test;');
+ yield array('postgres://root:pwd@localhost/test', 'pgsql:host=localhost;dbname=test;', 'root', 'pwd');
+ yield 'sqlite relative path' => array('sqlite://localhost/tmp/test', 'sqlite:tmp/test');
+ yield 'sqlite absolute path' => array('sqlite://localhost//tmp/test', 'sqlite:/tmp/test');
+ yield 'sqlite relative path without host' => array('sqlite:///tmp/test', 'sqlite:tmp/test');
+ yield 'sqlite absolute path without host' => array('sqlite3:////tmp/test', 'sqlite:/tmp/test');
+ yield array('sqlite://localhost/:memory:', 'sqlite::memory:');
+ yield array('mssql://localhost/test', 'sqlsrv:server=localhost;Database=test');
+ yield array('mssql://localhost:56/test', 'sqlsrv:server=localhost,56;Database=test');
+ }
+
+ private function createStream($content)
+ {
+ $stream = tmpfile();
+ fwrite($stream, $content);
+ fseek($stream, 0);
+
+ return $stream;
+ }
+}
+
+class MockPdo extends \PDO
+{
+ public $prepareResult;
+ private $driverName;
+ private $errorMode;
+
+ public function __construct($driverName = null, $errorMode = null)
+ {
+ $this->driverName = $driverName;
+ $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION;
+ }
+
+ public function getAttribute($attribute)
+ {
+ if (\PDO::ATTR_ERRMODE === $attribute) {
+ return $this->errorMode;
+ }
+
+ if (\PDO::ATTR_DRIVER_NAME === $attribute) {
+ return $this->driverName;
+ }
+
+ return parent::getAttribute($attribute);
+ }
+
+ public function prepare($statement, $driverOptions = array())
+ {
+ return is_callable($this->prepareResult)
+ ? call_user_func($this->prepareResult, $statement, $driverOptions)
+ : $this->prepareResult;
+ }
+
+ public function beginTransaction()
+ {
+ }
+
+ public function rollBack()
+ {
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php
new file mode 100644
index 0000000..b02c41a
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php
@@ -0,0 +1,189 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
+
+class StrictSessionHandlerTest extends TestCase
+{
+ public function testOpen()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('open')
+ ->with('path', 'name')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertInstanceOf('SessionUpdateTimestampHandlerInterface', $proxy);
+ $this->assertInstanceOf(AbstractSessionHandler::class, $proxy);
+ $this->assertTrue($proxy->open('path', 'name'));
+ }
+
+ public function testCloseSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('close')
+ ->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->close());
+ }
+
+ public function testValidateIdOK()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id'));
+ }
+
+ public function testValidateIdKO()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertFalse($proxy->validateId('id'));
+ }
+
+ public function testRead()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('data', $proxy->read('id'));
+ }
+
+ public function testReadWithValidateIdOK()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id'));
+ $this->assertSame('data', $proxy->read('id'));
+ }
+
+ public function testReadWithValidateIdMismatch()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->exactly(2))->method('read')
+ ->withConsecutive(array('id1'), array('id2'))
+ ->will($this->onConsecutiveCalls('data1', 'data2'));
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id1'));
+ $this->assertSame('data2', $proxy->read('id2'));
+ }
+
+ public function testUpdateTimestamp()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->updateTimestamp('id', 'data'));
+ }
+
+ public function testWrite()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->write('id', 'data'));
+ }
+
+ public function testWriteEmptyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->never())->method('write');
+ $handler->expects($this->once())->method('destroy')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertFalse($proxy->validateId('id'));
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', ''));
+ }
+
+ public function testWriteEmptyExistingSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $handler->expects($this->never())->method('write');
+ $handler->expects($this->once())->method('destroy')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('data', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', ''));
+ }
+
+ public function testDestroy()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('destroy')
+ ->with('id')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testDestroyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->once())->method('destroy')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testDestroyNonEmptyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $handler->expects($this->once())->method('destroy')
+ ->with('id')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', 'data'));
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testGc()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('gc')
+ ->with(123)->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->gc(123));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
new file mode 100644
index 0000000..898a7d1
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler;
+
+/**
+ * @author Adrien Brault
+ *
+ * @group legacy
+ */
+class WriteCheckSessionHandlerTest extends TestCase
+{
+ public function test()
+ {
+ $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('close')
+ ->with()
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($writeCheckSessionHandler->close());
+ }
+
+ public function testWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo', 'bar')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar'));
+ }
+
+ public function testSkippedWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('read')
+ ->with('foo')
+ ->will($this->returnValue('bar'))
+ ;
+
+ $wrappedSessionHandlerMock
+ ->expects($this->never())
+ ->method('write')
+ ;
+
+ $this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
+ $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar'));
+ }
+
+ public function testNonSkippedWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('read')
+ ->with('foo')
+ ->will($this->returnValue('bar'))
+ ;
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo', 'baZZZ')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
+ $this->assertTrue($writeCheckSessionHandler->write('foo', 'baZZZ'));
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php
new file mode 100644
index 0000000..69cf616
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php
@@ -0,0 +1,139 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
+
+/**
+ * Test class for MetadataBag.
+ *
+ * @group time-sensitive
+ */
+class MetadataBagTest extends TestCase
+{
+ /**
+ * @var MetadataBag
+ */
+ protected $bag;
+
+ protected $array = array();
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->bag = new MetadataBag();
+ $this->array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 0);
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->array = array();
+ $this->bag = null;
+ parent::tearDown();
+ }
+
+ public function testInitialize()
+ {
+ $sessionMetadata = array();
+
+ $bag1 = new MetadataBag();
+ $bag1->initialize($sessionMetadata);
+ $this->assertGreaterThanOrEqual(time(), $bag1->getCreated());
+ $this->assertEquals($bag1->getCreated(), $bag1->getLastUsed());
+
+ sleep(1);
+ $bag2 = new MetadataBag();
+ $bag2->initialize($sessionMetadata);
+ $this->assertEquals($bag1->getCreated(), $bag2->getCreated());
+ $this->assertEquals($bag1->getLastUsed(), $bag2->getLastUsed());
+ $this->assertEquals($bag2->getCreated(), $bag2->getLastUsed());
+
+ sleep(1);
+ $bag3 = new MetadataBag();
+ $bag3->initialize($sessionMetadata);
+ $this->assertEquals($bag1->getCreated(), $bag3->getCreated());
+ $this->assertGreaterThan($bag2->getLastUsed(), $bag3->getLastUsed());
+ $this->assertNotEquals($bag3->getCreated(), $bag3->getLastUsed());
+ }
+
+ public function testGetSetName()
+ {
+ $this->assertEquals('__metadata', $this->bag->getName());
+ $this->bag->setName('foo');
+ $this->assertEquals('foo', $this->bag->getName());
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_sf2_meta', $this->bag->getStorageKey());
+ }
+
+ public function testGetLifetime()
+ {
+ $bag = new MetadataBag();
+ $array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 1000);
+ $bag->initialize($array);
+ $this->assertEquals(1000, $bag->getLifetime());
+ }
+
+ public function testGetCreated()
+ {
+ $this->assertEquals(1234567, $this->bag->getCreated());
+ }
+
+ public function testGetLastUsed()
+ {
+ $this->assertLessThanOrEqual(time(), $this->bag->getLastUsed());
+ }
+
+ public function testClear()
+ {
+ $this->bag->clear();
+
+ // the clear method has no side effects, we just want to ensure it doesn't trigger any exceptions
+ $this->addToAssertionCount(1);
+ }
+
+ public function testSkipLastUsedUpdate()
+ {
+ $bag = new MetadataBag('', 30);
+ $timeStamp = time();
+
+ $created = $timeStamp - 15;
+ $sessionMetadata = array(
+ MetadataBag::CREATED => $created,
+ MetadataBag::UPDATED => $created,
+ MetadataBag::LIFETIME => 1000,
+ );
+ $bag->initialize($sessionMetadata);
+
+ $this->assertEquals($created, $sessionMetadata[MetadataBag::UPDATED]);
+ }
+
+ public function testDoesNotSkipLastUsedUpdate()
+ {
+ $bag = new MetadataBag('', 30);
+ $timeStamp = time();
+
+ $created = $timeStamp - 45;
+ $sessionMetadata = array(
+ MetadataBag::CREATED => $created,
+ MetadataBag::UPDATED => $created,
+ MetadataBag::LIFETIME => 1000,
+ );
+ $bag->initialize($sessionMetadata);
+
+ $this->assertEquals($timeStamp, $sessionMetadata[MetadataBag::UPDATED]);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php
new file mode 100644
index 0000000..82df554
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php
@@ -0,0 +1,131 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+
+/**
+ * Test class for MockArraySessionStorage.
+ *
+ * @author Drak
+ */
+class MockArraySessionStorageTest extends TestCase
+{
+ /**
+ * @var MockArraySessionStorage
+ */
+ private $storage;
+
+ /**
+ * @var AttributeBag
+ */
+ private $attributes;
+
+ /**
+ * @var FlashBag
+ */
+ private $flashes;
+
+ private $data;
+
+ protected function setUp()
+ {
+ $this->attributes = new AttributeBag();
+ $this->flashes = new FlashBag();
+
+ $this->data = array(
+ $this->attributes->getStorageKey() => array('foo' => 'bar'),
+ $this->flashes->getStorageKey() => array('notice' => 'hello'),
+ );
+
+ $this->storage = new MockArraySessionStorage();
+ $this->storage->registerBag($this->flashes);
+ $this->storage->registerBag($this->attributes);
+ $this->storage->setSessionData($this->data);
+ }
+
+ protected function tearDown()
+ {
+ $this->data = null;
+ $this->flashes = null;
+ $this->attributes = null;
+ $this->storage = null;
+ }
+
+ public function testStart()
+ {
+ $this->assertEquals('', $this->storage->getId());
+ $this->storage->start();
+ $id = $this->storage->getId();
+ $this->assertNotEquals('', $id);
+ $this->storage->start();
+ $this->assertEquals($id, $this->storage->getId());
+ }
+
+ public function testRegenerate()
+ {
+ $this->storage->start();
+ $id = $this->storage->getId();
+ $this->storage->regenerate();
+ $this->assertNotEquals($id, $this->storage->getId());
+ $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all());
+ $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll());
+
+ $id = $this->storage->getId();
+ $this->storage->regenerate(true);
+ $this->assertNotEquals($id, $this->storage->getId());
+ $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all());
+ $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll());
+ }
+
+ public function testGetId()
+ {
+ $this->assertEquals('', $this->storage->getId());
+ $this->storage->start();
+ $this->assertNotEquals('', $this->storage->getId());
+ }
+
+ public function testClearClearsBags()
+ {
+ $this->storage->clear();
+
+ $this->assertSame(array(), $this->storage->getBag('attributes')->all());
+ $this->assertSame(array(), $this->storage->getBag('flashes')->peekAll());
+ }
+
+ public function testClearStartsSession()
+ {
+ $this->storage->clear();
+
+ $this->assertTrue($this->storage->isStarted());
+ }
+
+ public function testClearWithNoBagsStartsSession()
+ {
+ $storage = new MockArraySessionStorage();
+
+ $storage->clear();
+
+ $this->assertTrue($storage->isStarted());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testUnstartedSave()
+ {
+ $this->storage->save();
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php
new file mode 100644
index 0000000..53accd3
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php
@@ -0,0 +1,127 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+
+/**
+ * Test class for MockFileSessionStorage.
+ *
+ * @author Drak
+ */
+class MockFileSessionStorageTest extends TestCase
+{
+ /**
+ * @var string
+ */
+ private $sessionDir;
+
+ /**
+ * @var MockFileSessionStorage
+ */
+ protected $storage;
+
+ protected function setUp()
+ {
+ $this->sessionDir = sys_get_temp_dir().'/sf2test';
+ $this->storage = $this->getStorage();
+ }
+
+ protected function tearDown()
+ {
+ $this->sessionDir = null;
+ $this->storage = null;
+ array_map('unlink', glob($this->sessionDir.'/*.session'));
+ if (is_dir($this->sessionDir)) {
+ rmdir($this->sessionDir);
+ }
+ }
+
+ public function testStart()
+ {
+ $this->assertEquals('', $this->storage->getId());
+ $this->assertTrue($this->storage->start());
+ $id = $this->storage->getId();
+ $this->assertNotEquals('', $this->storage->getId());
+ $this->assertTrue($this->storage->start());
+ $this->assertEquals($id, $this->storage->getId());
+ }
+
+ public function testRegenerate()
+ {
+ $this->storage->start();
+ $this->storage->getBag('attributes')->set('regenerate', 1234);
+ $this->storage->regenerate();
+ $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate'));
+ $this->storage->regenerate(true);
+ $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate'));
+ }
+
+ public function testGetId()
+ {
+ $this->assertEquals('', $this->storage->getId());
+ $this->storage->start();
+ $this->assertNotEquals('', $this->storage->getId());
+ }
+
+ public function testSave()
+ {
+ $this->storage->start();
+ $id = $this->storage->getId();
+ $this->assertNotEquals('108', $this->storage->getBag('attributes')->get('new'));
+ $this->assertFalse($this->storage->getBag('flashes')->has('newkey'));
+ $this->storage->getBag('attributes')->set('new', '108');
+ $this->storage->getBag('flashes')->set('newkey', 'test');
+ $this->storage->save();
+
+ $storage = $this->getStorage();
+ $storage->setId($id);
+ $storage->start();
+ $this->assertEquals('108', $storage->getBag('attributes')->get('new'));
+ $this->assertTrue($storage->getBag('flashes')->has('newkey'));
+ $this->assertEquals(array('test'), $storage->getBag('flashes')->peek('newkey'));
+ }
+
+ public function testMultipleInstances()
+ {
+ $storage1 = $this->getStorage();
+ $storage1->start();
+ $storage1->getBag('attributes')->set('foo', 'bar');
+ $storage1->save();
+
+ $storage2 = $this->getStorage();
+ $storage2->setId($storage1->getId());
+ $storage2->start();
+ $this->assertEquals('bar', $storage2->getBag('attributes')->get('foo'), 'values persist between instances');
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testSaveWithoutStart()
+ {
+ $storage1 = $this->getStorage();
+ $storage1->save();
+ }
+
+ private function getStorage()
+ {
+ $storage = new MockFileSessionStorage($this->sessionDir);
+ $storage->registerBag(new FlashBag());
+ $storage->registerBag(new AttributeBag());
+
+ return $storage;
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php
new file mode 100644
index 0000000..8fb8b42
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php
@@ -0,0 +1,277 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Test class for NativeSessionStorage.
+ *
+ * @author Drak
+ *
+ * These tests require separate processes.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NativeSessionStorageTest extends TestCase
+{
+ private $savePath;
+
+ protected function setUp()
+ {
+ $this->iniSet('session.save_handler', 'files');
+ $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test');
+ if (!is_dir($this->savePath)) {
+ mkdir($this->savePath);
+ }
+ }
+
+ protected function tearDown()
+ {
+ session_write_close();
+ array_map('unlink', glob($this->savePath.'/*'));
+ if (is_dir($this->savePath)) {
+ rmdir($this->savePath);
+ }
+
+ $this->savePath = null;
+ }
+
+ /**
+ * @return NativeSessionStorage
+ */
+ protected function getStorage(array $options = array())
+ {
+ $storage = new NativeSessionStorage($options);
+ $storage->registerBag(new AttributeBag());
+
+ return $storage;
+ }
+
+ public function testBag()
+ {
+ $storage = $this->getStorage();
+ $bag = new FlashBag();
+ $storage->registerBag($bag);
+ $this->assertSame($bag, $storage->getBag($bag->getName()));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRegisterBagException()
+ {
+ $storage = $this->getStorage();
+ $storage->getBag('non_existing');
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testRegisterBagForAStartedSessionThrowsException()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $storage->registerBag(new AttributeBag());
+ }
+
+ public function testGetId()
+ {
+ $storage = $this->getStorage();
+ $this->assertSame('', $storage->getId(), 'Empty ID before starting session');
+
+ $storage->start();
+ $id = $storage->getId();
+ $this->assertInternalType('string', $id);
+ $this->assertNotSame('', $id);
+
+ $storage->save();
+ $this->assertSame($id, $storage->getId(), 'ID stays after saving session');
+ }
+
+ public function testRegenerate()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $id = $storage->getId();
+ $storage->getBag('attributes')->set('lucky', 7);
+ $storage->regenerate();
+ $this->assertNotEquals($id, $storage->getId());
+ $this->assertEquals(7, $storage->getBag('attributes')->get('lucky'));
+ }
+
+ public function testRegenerateDestroy()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $id = $storage->getId();
+ $storage->getBag('attributes')->set('legs', 11);
+ $storage->regenerate(true);
+ $this->assertNotEquals($id, $storage->getId());
+ $this->assertEquals(11, $storage->getBag('attributes')->get('legs'));
+ }
+
+ public function testSessionGlobalIsUpToDateAfterIdRegeneration()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $storage->getBag('attributes')->set('lucky', 7);
+ $storage->regenerate();
+ $storage->getBag('attributes')->set('lucky', 42);
+
+ $this->assertEquals(42, $_SESSION['_sf2_attributes']['lucky']);
+ }
+
+ public function testRegenerationFailureDoesNotFlagStorageAsStarted()
+ {
+ $storage = $this->getStorage();
+ $this->assertFalse($storage->regenerate());
+ $this->assertFalse($storage->isStarted());
+ }
+
+ public function testDefaultSessionCacheLimiter()
+ {
+ $this->iniSet('session.cache_limiter', 'nocache');
+
+ $storage = new NativeSessionStorage();
+ $this->assertEquals('', ini_get('session.cache_limiter'));
+ }
+
+ public function testExplicitSessionCacheLimiter()
+ {
+ $this->iniSet('session.cache_limiter', 'nocache');
+
+ $storage = new NativeSessionStorage(array('cache_limiter' => 'public'));
+ $this->assertEquals('public', ini_get('session.cache_limiter'));
+ }
+
+ public function testCookieOptions()
+ {
+ $options = array(
+ 'cookie_lifetime' => 123456,
+ 'cookie_path' => '/my/cookie/path',
+ 'cookie_domain' => 'symfony.example.com',
+ 'cookie_secure' => true,
+ 'cookie_httponly' => false,
+ );
+
+ $this->getStorage($options);
+ $temp = session_get_cookie_params();
+ $gco = array();
+
+ foreach ($temp as $key => $value) {
+ $gco['cookie_'.$key] = $value;
+ }
+
+ $this->assertEquals($options, $gco);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetSaveHandlerException()
+ {
+ $storage = $this->getStorage();
+ $storage->setSaveHandler(new \stdClass());
+ }
+
+ public function testSetSaveHandler()
+ {
+ $this->iniSet('session.save_handler', 'files');
+ $storage = $this->getStorage();
+ $storage->setSaveHandler();
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(null);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(new SessionHandlerProxy(new NativeFileSessionHandler()));
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(new NativeFileSessionHandler());
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler()));
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(new NullSessionHandler());
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testStarted()
+ {
+ $storage = $this->getStorage();
+
+ $this->assertFalse($storage->getSaveHandler()->isActive());
+ $this->assertFalse($storage->isStarted());
+
+ session_start();
+ $this->assertTrue(isset($_SESSION));
+ $this->assertTrue($storage->getSaveHandler()->isActive());
+
+ // PHP session might have started, but the storage driver has not, so false is correct here
+ $this->assertFalse($storage->isStarted());
+
+ $key = $storage->getMetadataBag()->getStorageKey();
+ $this->assertArrayNotHasKey($key, $_SESSION);
+ $storage->start();
+ }
+
+ public function testRestart()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $id = $storage->getId();
+ $storage->getBag('attributes')->set('lucky', 7);
+ $storage->save();
+ $storage->start();
+ $this->assertSame($id, $storage->getId(), 'Same session ID after restarting');
+ $this->assertSame(7, $storage->getBag('attributes')->get('lucky'), 'Data still available');
+ }
+
+ public function testCanCreateNativeSessionStorageWhenSessionAlreadyStarted()
+ {
+ session_start();
+ $this->getStorage();
+
+ // Assert no exception has been thrown by `getStorage()`
+ $this->addToAssertionCount(1);
+ }
+
+ public function testSetSessionOptionsOnceSessionStartedIsIgnored()
+ {
+ session_start();
+ $this->getStorage(array(
+ 'name' => 'something-else',
+ ));
+
+ // Assert no exception has been thrown by `getStorage()`
+ $this->addToAssertionCount(1);
+ }
+
+ public function testGetBagsOnceSessionStartedIsIgnored()
+ {
+ session_start();
+ $bag = new AttributeBag();
+ $bag->setName('flashes');
+
+ $storage = $this->getStorage();
+ $storage->registerBag($bag);
+
+ $this->assertEquals($storage->getBag('flashes'), $bag);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
new file mode 100644
index 0000000..958dc0b
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
@@ -0,0 +1,96 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+
+/**
+ * Test class for PhpSessionStorage.
+ *
+ * @author Drak
+ *
+ * These tests require separate processes.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class PhpBridgeSessionStorageTest extends TestCase
+{
+ private $savePath;
+
+ protected function setUp()
+ {
+ $this->iniSet('session.save_handler', 'files');
+ $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test');
+ if (!is_dir($this->savePath)) {
+ mkdir($this->savePath);
+ }
+ }
+
+ protected function tearDown()
+ {
+ session_write_close();
+ array_map('unlink', glob($this->savePath.'/*'));
+ if (is_dir($this->savePath)) {
+ rmdir($this->savePath);
+ }
+
+ $this->savePath = null;
+ }
+
+ /**
+ * @return PhpBridgeSessionStorage
+ */
+ protected function getStorage()
+ {
+ $storage = new PhpBridgeSessionStorage();
+ $storage->registerBag(new AttributeBag());
+
+ return $storage;
+ }
+
+ public function testPhpSession()
+ {
+ $storage = $this->getStorage();
+
+ $this->assertFalse($storage->getSaveHandler()->isActive());
+ $this->assertFalse($storage->isStarted());
+
+ session_start();
+ $this->assertTrue(isset($_SESSION));
+ // in PHP 5.4 we can reliably detect a session started
+ $this->assertTrue($storage->getSaveHandler()->isActive());
+ // PHP session might have started, but the storage driver has not, so false is correct here
+ $this->assertFalse($storage->isStarted());
+
+ $key = $storage->getMetadataBag()->getStorageKey();
+ $this->assertArrayNotHasKey($key, $_SESSION);
+ $storage->start();
+ $this->assertArrayHasKey($key, $_SESSION);
+ }
+
+ public function testClear()
+ {
+ $storage = $this->getStorage();
+ session_start();
+ $_SESSION['drak'] = 'loves symfony';
+ $storage->getBag('attributes')->set('symfony', 'greatness');
+ $key = $storage->getBag('attributes')->getStorageKey();
+ $this->assertEquals($_SESSION[$key], array('symfony' => 'greatness'));
+ $this->assertEquals($_SESSION['drak'], 'loves symfony');
+ $storage->clear();
+ $this->assertEquals($_SESSION[$key], array());
+ $this->assertEquals($_SESSION['drak'], 'loves symfony');
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
new file mode 100644
index 0000000..cbb291f
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Test class for AbstractProxy.
+ *
+ * @author Drak
+ */
+class AbstractProxyTest extends TestCase
+{
+ /**
+ * @var AbstractProxy
+ */
+ protected $proxy;
+
+ protected function setUp()
+ {
+ $this->proxy = $this->getMockForAbstractClass(AbstractProxy::class);
+ }
+
+ protected function tearDown()
+ {
+ $this->proxy = null;
+ }
+
+ public function testGetSaveHandlerName()
+ {
+ $this->assertNull($this->proxy->getSaveHandlerName());
+ }
+
+ public function testIsSessionHandlerInterface()
+ {
+ $this->assertFalse($this->proxy->isSessionHandlerInterface());
+ $sh = new SessionHandlerProxy(new \SessionHandler());
+ $this->assertTrue($sh->isSessionHandlerInterface());
+ }
+
+ public function testIsWrapper()
+ {
+ $this->assertFalse($this->proxy->isWrapper());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testIsActive()
+ {
+ $this->assertFalse($this->proxy->isActive());
+ session_start();
+ $this->assertTrue($this->proxy->isActive());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testName()
+ {
+ $this->assertEquals(session_name(), $this->proxy->getName());
+ $this->proxy->setName('foo');
+ $this->assertEquals('foo', $this->proxy->getName());
+ $this->assertEquals(session_name(), $this->proxy->getName());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ * @expectedException \LogicException
+ */
+ public function testNameException()
+ {
+ session_start();
+ $this->proxy->setName('foo');
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testId()
+ {
+ $this->assertEquals(session_id(), $this->proxy->getId());
+ $this->proxy->setId('foo');
+ $this->assertEquals('foo', $this->proxy->getId());
+ $this->assertEquals(session_id(), $this->proxy->getId());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ * @expectedException \LogicException
+ */
+ public function testIdException()
+ {
+ session_start();
+ $this->proxy->setId('foo');
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php
new file mode 100644
index 0000000..ed4fee6
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
+
+/**
+ * Test class for NativeProxy.
+ *
+ * @group legacy
+ *
+ * @author Drak
+ */
+class NativeProxyTest extends TestCase
+{
+ public function testIsWrapper()
+ {
+ $proxy = new NativeProxy();
+ $this->assertFalse($proxy->isWrapper());
+ }
+
+ public function testGetSaveHandlerName()
+ {
+ $name = ini_get('session.save_handler');
+ $proxy = new NativeProxy();
+ $this->assertEquals($name, $proxy->getSaveHandlerName());
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
new file mode 100644
index 0000000..6828253
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
@@ -0,0 +1,124 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Tests for SessionHandlerProxy class.
+ *
+ * @author Drak
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class SessionHandlerProxyTest extends TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_Matcher
+ */
+ private $mock;
+
+ /**
+ * @var SessionHandlerProxy
+ */
+ private $proxy;
+
+ protected function setUp()
+ {
+ $this->mock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $this->proxy = new SessionHandlerProxy($this->mock);
+ }
+
+ protected function tearDown()
+ {
+ $this->mock = null;
+ $this->proxy = null;
+ }
+
+ public function testOpenTrue()
+ {
+ $this->mock->expects($this->once())
+ ->method('open')
+ ->will($this->returnValue(true));
+
+ $this->assertFalse($this->proxy->isActive());
+ $this->proxy->open('name', 'id');
+ $this->assertFalse($this->proxy->isActive());
+ }
+
+ public function testOpenFalse()
+ {
+ $this->mock->expects($this->once())
+ ->method('open')
+ ->will($this->returnValue(false));
+
+ $this->assertFalse($this->proxy->isActive());
+ $this->proxy->open('name', 'id');
+ $this->assertFalse($this->proxy->isActive());
+ }
+
+ public function testClose()
+ {
+ $this->mock->expects($this->once())
+ ->method('close')
+ ->will($this->returnValue(true));
+
+ $this->assertFalse($this->proxy->isActive());
+ $this->proxy->close();
+ $this->assertFalse($this->proxy->isActive());
+ }
+
+ public function testCloseFalse()
+ {
+ $this->mock->expects($this->once())
+ ->method('close')
+ ->will($this->returnValue(false));
+
+ $this->assertFalse($this->proxy->isActive());
+ $this->proxy->close();
+ $this->assertFalse($this->proxy->isActive());
+ }
+
+ public function testRead()
+ {
+ $this->mock->expects($this->once())
+ ->method('read');
+
+ $this->proxy->read('id');
+ }
+
+ public function testWrite()
+ {
+ $this->mock->expects($this->once())
+ ->method('write');
+
+ $this->proxy->write('id', 'data');
+ }
+
+ public function testDestroy()
+ {
+ $this->mock->expects($this->once())
+ ->method('destroy');
+
+ $this->proxy->destroy('id');
+ }
+
+ public function testGc()
+ {
+ $this->mock->expects($this->once())
+ ->method('gc');
+
+ $this->proxy->gc(86400);
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php b/assets/php/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php
new file mode 100644
index 0000000..c2ded99
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php
@@ -0,0 +1,126 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+class StreamedResponseTest extends TestCase
+{
+ public function testConstructor()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; }, 404, array('Content-Type' => 'text/plain'));
+
+ $this->assertEquals(404, $response->getStatusCode());
+ $this->assertEquals('text/plain', $response->headers->get('Content-Type'));
+ }
+
+ public function testPrepareWith11Protocol()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $request = Request::create('/');
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1');
+
+ $response->prepare($request);
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ $this->assertNotEquals('chunked', $response->headers->get('Transfer-Encoding'), 'Apache assumes responses with a Transfer-Encoding header set to chunked to already be encoded.');
+ }
+
+ public function testPrepareWith10Protocol()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $request = Request::create('/');
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0');
+
+ $response->prepare($request);
+
+ $this->assertEquals('1.0', $response->getProtocolVersion());
+ $this->assertNull($response->headers->get('Transfer-Encoding'));
+ }
+
+ public function testPrepareWithHeadRequest()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; }, 200, array('Content-Length' => '123'));
+ $request = Request::create('/', 'HEAD');
+
+ $response->prepare($request);
+
+ $this->assertSame('123', $response->headers->get('Content-Length'));
+ }
+
+ public function testPrepareWithCacheHeaders()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; }, 200, array('Cache-Control' => 'max-age=600, public'));
+ $request = Request::create('/', 'GET');
+
+ $response->prepare($request);
+ $this->assertEquals('max-age=600, public', $response->headers->get('Cache-Control'));
+ }
+
+ public function testSendContent()
+ {
+ $called = 0;
+
+ $response = new StreamedResponse(function () use (&$called) { ++$called; });
+
+ $response->sendContent();
+ $this->assertEquals(1, $called);
+
+ $response->sendContent();
+ $this->assertEquals(1, $called);
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testSendContentWithNonCallable()
+ {
+ $response = new StreamedResponse(null);
+ $response->sendContent();
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testSetContent()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $response->setContent('foo');
+ }
+
+ public function testGetContent()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $this->assertFalse($response->getContent());
+ }
+
+ public function testCreate()
+ {
+ $response = StreamedResponse::create(function () {}, 204);
+
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response);
+ $this->assertEquals(204, $response->getStatusCode());
+ }
+
+ public function testReturnThis()
+ {
+ $response = new StreamedResponse(function () {});
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendContent());
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendContent());
+
+ $response = new StreamedResponse(function () {});
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendHeaders());
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendHeaders());
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng b/assets/php/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng
new file mode 100644
index 0000000..73708ca
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng b/assets/php/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng
new file mode 100644
index 0000000..b9c3ca9
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ uri
+
+
+
+ rfc
+
+
+ (rfc|bcp|std)\d+
+
+
+
+
+ rfc-errata
+
+
+
+ draft
+
+
+ (draft|RFC)(-[a-zA-Z0-9]+)+
+
+
+
+
+ registry
+
+
+
+ person
+
+
+
+ text
+
+
+ note
+
+
+
+ unicode
+
+
+ ucd\d+\.\d+\.\d+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (\d+|0x[\da-fA-F]+)(\s*-\s*(\d+|0x[\da-fA-F]+))?
+
+
+
+
+
+
+
+
+
+
+
+
+ 0x[0-9]{8}
+
+
+
+
+
+ [0-1]+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ legacy
+ mib
+ template
+ json
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/http-foundation/composer.json b/assets/php/vendor/symfony/http-foundation/composer.json
new file mode 100644
index 0000000..f6c6f2e
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "symfony/http-foundation",
+ "type": "library",
+ "description": "Symfony HttpFoundation Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-mbstring": "~1.1",
+ "symfony/polyfill-php70": "~1.6"
+ },
+ "require-dev": {
+ "symfony/expression-language": "~2.8|~3.0|~4.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/http-foundation/phpunit.xml.dist b/assets/php/vendor/symfony/http-foundation/phpunit.xml.dist
new file mode 100644
index 0000000..c1d61f8
--- /dev/null
+++ b/assets/php/vendor/symfony/http-foundation/phpunit.xml.dist
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Resources
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/assets/php/vendor/symfony/polyfill-mbstring/LICENSE b/assets/php/vendor/symfony/polyfill-mbstring/LICENSE
new file mode 100644
index 0000000..24fa32c
--- /dev/null
+++ b/assets/php/vendor/symfony/polyfill-mbstring/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2018 Fabien Potencier
+
+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.
diff --git a/assets/php/vendor/symfony/polyfill-mbstring/Mbstring.php b/assets/php/vendor/symfony/polyfill-mbstring/Mbstring.php
new file mode 100644
index 0000000..67cf9ab
--- /dev/null
+++ b/assets/php/vendor/symfony/polyfill-mbstring/Mbstring.php
@@ -0,0 +1,791 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Mbstring;
+
+/**
+ * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
+ *
+ * Implemented:
+ * - mb_chr - Returns a specific character from its Unicode code point
+ * - mb_convert_encoding - Convert character encoding
+ * - mb_convert_variables - Convert character code in variable(s)
+ * - mb_decode_mimeheader - Decode string in MIME header field
+ * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
+ * - mb_decode_numericentity - Decode HTML numeric string reference to character
+ * - mb_encode_numericentity - Encode character to HTML numeric string reference
+ * - mb_convert_case - Perform case folding on a string
+ * - mb_detect_encoding - Detect character encoding
+ * - mb_get_info - Get internal settings of mbstring
+ * - mb_http_input - Detect HTTP input character encoding
+ * - mb_http_output - Set/Get HTTP output character encoding
+ * - mb_internal_encoding - Set/Get internal character encoding
+ * - mb_list_encodings - Returns an array of all supported encodings
+ * - mb_ord - Returns the Unicode code point of a character
+ * - mb_output_handler - Callback function converts character encoding in output buffer
+ * - mb_scrub - Replaces ill-formed byte sequences with substitute characters
+ * - mb_strlen - Get string length
+ * - mb_strpos - Find position of first occurrence of string in a string
+ * - mb_strrpos - Find position of last occurrence of a string in a string
+ * - mb_strtolower - Make a string lowercase
+ * - mb_strtoupper - Make a string uppercase
+ * - mb_substitute_character - Set/Get substitution character
+ * - mb_substr - Get part of string
+ * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
+ * - mb_stristr - Finds first occurrence of a string within another, case insensitive
+ * - mb_strrchr - Finds the last occurrence of a character in a string within another
+ * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
+ * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
+ * - mb_strstr - Finds first occurrence of a string within anothers
+ * - mb_strwidth - Return width of string
+ * - mb_substr_count - Count the number of substring occurrences
+ *
+ * Not implemented:
+ * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
+ * - mb_ereg_* - Regular expression with multibyte support
+ * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
+ * - mb_preferred_mime_name - Get MIME charset string
+ * - mb_regex_encoding - Returns current encoding for multibyte regex as string
+ * - mb_regex_set_options - Set/Get the default options for mbregex functions
+ * - mb_send_mail - Send encoded mail
+ * - mb_split - Split multibyte string using regular expression
+ * - mb_strcut - Get part of string
+ * - mb_strimwidth - Get truncated string with specified width
+ *
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+final class Php70
+{
+ public static function intdiv($dividend, $divisor)
+ {
+ $dividend = self::intArg($dividend, __FUNCTION__, 1);
+ $divisor = self::intArg($divisor, __FUNCTION__, 2);
+
+ if (0 === $divisor) {
+ throw new \DivisionByZeroError('Division by zero');
+ }
+ if (-1 === $divisor && ~PHP_INT_MAX === $dividend) {
+ throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer');
+ }
+
+ return ($dividend - ($dividend % $divisor)) / $divisor;
+ }
+
+ public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0)
+ {
+ $count = 0;
+ $result = (string) $subject;
+ if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) {
+ return $result;
+ }
+
+ foreach ($patterns as $pattern => $callback) {
+ $result = preg_replace_callback($pattern, $callback, $result, $limit, $c);
+ $count += $c;
+ }
+
+ return $result;
+ }
+
+ public static function error_clear_last()
+ {
+ static $handler;
+ if (!$handler) {
+ $handler = function() { return false; };
+ }
+ set_error_handler($handler);
+ @trigger_error('');
+ restore_error_handler();
+ }
+
+ public static function intArg($value, $caller, $pos)
+ {
+ if (is_int($value)) {
+ return $value;
+ }
+ if (!is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) {
+ throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, gettype($value)));
+ }
+
+ return (int) $value;
+ }
+}
diff --git a/assets/php/vendor/symfony/polyfill-php70/README.md b/assets/php/vendor/symfony/polyfill-php70/README.md
new file mode 100644
index 0000000..04988c6
--- /dev/null
+++ b/assets/php/vendor/symfony/polyfill-php70/README.md
@@ -0,0 +1,28 @@
+Symfony Polyfill / Php70
+========================
+
+This component provides features unavailable in releases prior to PHP 7.0:
+
+- [`intdiv`](http://php.net/intdiv)
+- [`preg_replace_callback_array`](http://php.net/preg_replace_callback_array)
+- [`error_clear_last`](http://php.net/error_clear_last)
+- `random_bytes` and `random_int` (from [paragonie/random_compat](https://github.com/paragonie/random_compat))
+- [`*Error` throwable classes](http://php.net/Error)
+- [`PHP_INT_MIN`](http://php.net/manual/en/reserved.constants.php#constant.php-int-min)
+- `SessionUpdateTimestampHandlerInterface`
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+Compatibility notes
+===================
+
+To write portable code between PHP5 and PHP7, some care must be taken:
+- `\*Error` exceptions must be caught before `\Exception`;
+- after calling `error_clear_last()`, the result of `$e = error_get_last()` must be
+ verified using `isset($e['message'][0])` instead of `null !== $e`.
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/assets/php/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/assets/php/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php
new file mode 100644
index 0000000..6819124
--- /dev/null
+++ b/assets/php/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php
@@ -0,0 +1,5 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php70 as p;
+
+if (PHP_VERSION_ID < 70000) {
+ if (!defined('PHP_INT_MIN')) {
+ define('PHP_INT_MIN', ~PHP_INT_MAX);
+ }
+ if (!function_exists('intdiv')) {
+ function intdiv($dividend, $divisor) { return p\Php70::intdiv($dividend, $divisor); }
+ }
+ if (!function_exists('preg_replace_callback_array')) {
+ function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) { return p\Php70::preg_replace_callback_array($patterns, $subject, $limit, $count); }
+ }
+ if (!function_exists('error_clear_last')) {
+ function error_clear_last() { return p\Php70::error_clear_last(); }
+ }
+}
diff --git a/assets/php/vendor/symfony/polyfill-php70/composer.json b/assets/php/vendor/symfony/polyfill-php70/composer.json
new file mode 100644
index 0000000..88ff357
--- /dev/null
+++ b/assets/php/vendor/symfony/polyfill-php70/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/polyfill-php70",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3",
+ "paragonie/random_compat": "~1.0|~2.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php70\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.7-dev"
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/.gitignore b/assets/php/vendor/symfony/routing/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/assets/php/vendor/symfony/routing/Annotation/Route.php b/assets/php/vendor/symfony/routing/Annotation/Route.php
new file mode 100644
index 0000000..5b3cbea
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Annotation/Route.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Annotation;
+
+/**
+ * Annotation class for @Route().
+ *
+ * @Annotation
+ * @Target({"CLASS", "METHOD"})
+ *
+ * @author Fabien Potencier
+ */
+class Route
+{
+ private $path;
+ private $name;
+ private $requirements = array();
+ private $options = array();
+ private $defaults = array();
+ private $host;
+ private $methods = array();
+ private $schemes = array();
+ private $condition;
+
+ /**
+ * @param array $data An array of key/value parameters
+ *
+ * @throws \BadMethodCallException
+ */
+ public function __construct(array $data)
+ {
+ if (isset($data['value'])) {
+ $data['path'] = $data['value'];
+ unset($data['value']);
+ }
+
+ foreach ($data as $key => $value) {
+ $method = 'set'.str_replace('_', '', $key);
+ if (!method_exists($this, $method)) {
+ throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, get_class($this)));
+ }
+ $this->$method($value);
+ }
+ }
+
+ public function setPath($path)
+ {
+ $this->path = $path;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ public function setHost($pattern)
+ {
+ $this->host = $pattern;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setRequirements($requirements)
+ {
+ $this->requirements = $requirements;
+ }
+
+ public function getRequirements()
+ {
+ return $this->requirements;
+ }
+
+ public function setOptions($options)
+ {
+ $this->options = $options;
+ }
+
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ public function setDefaults($defaults)
+ {
+ $this->defaults = $defaults;
+ }
+
+ public function getDefaults()
+ {
+ return $this->defaults;
+ }
+
+ public function setSchemes($schemes)
+ {
+ $this->schemes = is_array($schemes) ? $schemes : array($schemes);
+ }
+
+ public function getSchemes()
+ {
+ return $this->schemes;
+ }
+
+ public function setMethods($methods)
+ {
+ $this->methods = is_array($methods) ? $methods : array($methods);
+ }
+
+ public function getMethods()
+ {
+ return $this->methods;
+ }
+
+ public function setCondition($condition)
+ {
+ $this->condition = $condition;
+ }
+
+ public function getCondition()
+ {
+ return $this->condition;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/CHANGELOG.md b/assets/php/vendor/symfony/routing/CHANGELOG.md
new file mode 100644
index 0000000..e278f8b
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/CHANGELOG.md
@@ -0,0 +1,228 @@
+CHANGELOG
+=========
+
+3.4.0
+-----
+
+ * Added `NoConfigurationException`.
+ * Added the possibility to define a prefix for all routes of a controller via @Route(name="prefix_")
+ * Added support for prioritized routing loaders.
+ * Add matched and default parameters to redirect responses
+ * Added support for a `controller` keyword for configuring route controllers in YAML and XML configurations.
+
+3.3.0
+-----
+
+ * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0.
+ * router.options.generator_class
+ * router.options.generator_base_class
+ * router.options.generator_dumper_class
+ * router.options.matcher_class
+ * router.options.matcher_base_class
+ * router.options.matcher_dumper_class
+ * router.options.matcher.cache_class
+ * router.options.generator.cache_class
+
+3.2.0
+-----
+
+ * Added support for `bool`, `int`, `float`, `string`, `list` and `map` defaults in XML configurations.
+ * Added support for UTF-8 requirements
+
+2.8.0
+-----
+
+ * allowed specifying a directory to recursively load all routing configuration files it contains
+ * Added ObjectRouteLoader and ServiceRouteLoader that allow routes to be loaded
+ by calling a method on an object/service.
+ * [DEPRECATION] Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method.
+ Use the constants defined in the `UrlGeneratorInterface` instead.
+
+ Before:
+
+ ```php
+ $router->generate('blog_show', array('slug' => 'my-blog-post'), true);
+ ```
+
+ After:
+
+ ```php
+ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+ $router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL);
+ ```
+
+2.5.0
+-----
+
+ * [DEPRECATION] The `ApacheMatcherDumper` and `ApacheUrlMatcher` were deprecated and
+ will be removed in Symfony 3.0, since the performance gains were minimal and
+ it's hard to replicate the behaviour of PHP implementation.
+
+2.3.0
+-----
+
+ * added RequestContext::getQueryString()
+
+2.2.0
+-----
+
+ * [DEPRECATION] Several route settings have been renamed (the old ones will be removed in 3.0):
+
+ * The `pattern` setting for a route has been deprecated in favor of `path`
+ * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings
+
+ Before:
+
+ ```yaml
+ article_edit:
+ pattern: /article/{id}
+ requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' }
+ ```
+
+ ```xml
+
+ POST|PUT
+ https
+ \d+
+
+ ```
+
+ ```php
+ $route = new Route();
+ $route->setPattern('/article/{id}');
+ $route->setRequirement('_method', 'POST|PUT');
+ $route->setRequirement('_scheme', 'https');
+ ```
+
+ After:
+
+ ```yaml
+ article_edit:
+ path: /article/{id}
+ methods: [POST, PUT]
+ schemes: https
+ requirements: { 'id': '\d+' }
+ ```
+
+ ```xml
+
+ \d+
+
+ ```
+
+ ```php
+ $route = new Route();
+ $route->setPath('/article/{id}');
+ $route->setMethods(array('POST', 'PUT'));
+ $route->setSchemes('https');
+ ```
+
+ * [BC BREAK] RouteCollection does not behave like a tree structure anymore but as
+ a flat array of Routes. So when using PHP to build the RouteCollection, you must
+ make sure to add routes to the sub-collection before adding it to the parent
+ collection (this is not relevant when using YAML or XML for Route definitions).
+
+ Before:
+
+ ```php
+ $rootCollection = new RouteCollection();
+ $subCollection = new RouteCollection();
+ $rootCollection->addCollection($subCollection);
+ $subCollection->add('foo', new Route('/foo'));
+ ```
+
+ After:
+
+ ```php
+ $rootCollection = new RouteCollection();
+ $subCollection = new RouteCollection();
+ $subCollection->add('foo', new Route('/foo'));
+ $rootCollection->addCollection($subCollection);
+ ```
+
+ Also one must call `addCollection` from the bottom to the top hierarchy.
+ So the correct sequence is the following (and not the reverse):
+
+ ```php
+ $childCollection->addCollection($grandchildCollection);
+ $rootCollection->addCollection($childCollection);
+ ```
+
+ * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()`
+ have been deprecated and will be removed in Symfony 2.3.
+ * [BC BREAK] Misusing the `RouteCollection::addPrefix` method to add defaults, requirements
+ or options without adding a prefix is not supported anymore. So if you called `addPrefix`
+ with an empty prefix or `/` only (both have no relevance), like
+ `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)`
+ you need to use the new dedicated methods `addDefaults($defaultsArray)`,
+ `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead.
+ * [DEPRECATION] The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated
+ because adding options has nothing to do with adding a path prefix. If you want to add options
+ to all child routes of a RouteCollection, you can use `addOptions()`.
+ * [DEPRECATION] The method `RouteCollection::getPrefix()` has been deprecated
+ because it suggested that all routes in the collection would have this prefix, which is
+ not necessarily true. On top of that, since there is no tree structure anymore, this method
+ is also useless. Don't worry about performance, prefix optimization for matching is still done
+ in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities.
+ * [DEPRECATION] `RouteCollection::addCollection(RouteCollection $collection)` should now only be
+ used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options`
+ will still work, but have been deprecated. The `addPrefix` method should be used for this
+ use-case instead.
+ Before: `$parentCollection->addCollection($collection, '/prefix', array(...), array(...))`
+ After:
+ ```php
+ $collection->addPrefix('/prefix', array(...), array(...));
+ $parentCollection->addCollection($collection);
+ ```
+ * added support for the method default argument values when defining a @Route
+ * Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`.
+ * Characters that function as separator between placeholders are now whitelisted
+ to fix routes with normal text around a variable, e.g. `/prefix{var}suffix`.
+ * [BC BREAK] The default requirement of a variable has been changed slightly.
+ Previously it disallowed the previous and the next char around a variable. Now
+ it disallows the slash (`/`) and the next char. Using the previous char added
+ no value and was problematic because the route `/index.{_format}` would be
+ matched by `/index.ht/ml`.
+ * The default requirement now uses possessive quantifiers when possible which
+ improves matching performance by up to 20% because it prevents backtracking
+ when it's not needed.
+ * The ConfigurableRequirementsInterface can now also be used to disable the requirements
+ check on URL generation completely by calling `setStrictRequirements(null)`. It
+ improves performance in production environment as you should know that params always
+ pass the requirements (otherwise it would break your link anyway).
+ * There is no restriction on the route name anymore. So non-alphanumeric characters
+ are now also allowed.
+ * [BC BREAK] `RouteCompilerInterface::compile(Route $route)` was made static
+ (only relevant if you implemented your own RouteCompiler).
+ * Added possibility to generate relative paths and network paths in the UrlGenerator, e.g.
+ "../parent-file" and "//example.com/dir/file". The third parameter in
+ `UrlGeneratorInterface::generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)`
+ now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for
+ claritiy. The old method calls with a Boolean parameter will continue to work because they
+ equal the signature using the constants.
+
+2.1.0
+-----
+
+ * added RequestMatcherInterface
+ * added RequestContext::fromRequest()
+ * the UrlMatcher does not throw a \LogicException anymore when the required
+ scheme is not the current one
+ * added TraceableUrlMatcher
+ * added the possibility to define options, default values and requirements
+ for placeholders in prefix, including imported routes
+ * added RouterInterface::getRouteCollection
+ * [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they
+ were decoded twice before. Note that the `urldecode()` calls have been
+ changed for a single `rawurldecode()` in order to support `+` for input
+ paths.
+ * added RouteCollection::getRoot method to retrieve the root of a
+ RouteCollection tree
+ * [BC BREAK] made RouteCollection::setParent private which could not have
+ been used anyway without creating inconsistencies
+ * [BC BREAK] RouteCollection::remove also removes a route from parent
+ collections (not only from its children)
+ * added ConfigurableRequirementsInterface that allows to disable exceptions
+ (and generate empty URLs instead) when generating a route with an invalid
+ parameter value
diff --git a/assets/php/vendor/symfony/routing/CompiledRoute.php b/assets/php/vendor/symfony/routing/CompiledRoute.php
new file mode 100644
index 0000000..8ecf515
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/CompiledRoute.php
@@ -0,0 +1,169 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+/**
+ * CompiledRoutes are returned by the RouteCompiler class.
+ *
+ * @author Fabien Potencier
+ */
+class CompiledRoute implements \Serializable
+{
+ private $variables;
+ private $tokens;
+ private $staticPrefix;
+ private $regex;
+ private $pathVariables;
+ private $hostVariables;
+ private $hostRegex;
+ private $hostTokens;
+
+ /**
+ * @param string $staticPrefix The static prefix of the compiled route
+ * @param string $regex The regular expression to use to match this route
+ * @param array $tokens An array of tokens to use to generate URL for this route
+ * @param array $pathVariables An array of path variables
+ * @param string|null $hostRegex Host regex
+ * @param array $hostTokens Host tokens
+ * @param array $hostVariables An array of host variables
+ * @param array $variables An array of variables (variables defined in the path and in the host patterns)
+ */
+ public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array())
+ {
+ $this->staticPrefix = (string) $staticPrefix;
+ $this->regex = $regex;
+ $this->tokens = $tokens;
+ $this->pathVariables = $pathVariables;
+ $this->hostRegex = $hostRegex;
+ $this->hostTokens = $hostTokens;
+ $this->hostVariables = $hostVariables;
+ $this->variables = $variables;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function serialize()
+ {
+ return serialize(array(
+ 'vars' => $this->variables,
+ 'path_prefix' => $this->staticPrefix,
+ 'path_regex' => $this->regex,
+ 'path_tokens' => $this->tokens,
+ 'path_vars' => $this->pathVariables,
+ 'host_regex' => $this->hostRegex,
+ 'host_tokens' => $this->hostTokens,
+ 'host_vars' => $this->hostVariables,
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unserialize($serialized)
+ {
+ if (\PHP_VERSION_ID >= 70000) {
+ $data = unserialize($serialized, array('allowed_classes' => false));
+ } else {
+ $data = unserialize($serialized);
+ }
+
+ $this->variables = $data['vars'];
+ $this->staticPrefix = $data['path_prefix'];
+ $this->regex = $data['path_regex'];
+ $this->tokens = $data['path_tokens'];
+ $this->pathVariables = $data['path_vars'];
+ $this->hostRegex = $data['host_regex'];
+ $this->hostTokens = $data['host_tokens'];
+ $this->hostVariables = $data['host_vars'];
+ }
+
+ /**
+ * Returns the static prefix.
+ *
+ * @return string The static prefix
+ */
+ public function getStaticPrefix()
+ {
+ return $this->staticPrefix;
+ }
+
+ /**
+ * Returns the regex.
+ *
+ * @return string The regex
+ */
+ public function getRegex()
+ {
+ return $this->regex;
+ }
+
+ /**
+ * Returns the host regex.
+ *
+ * @return string|null The host regex or null
+ */
+ public function getHostRegex()
+ {
+ return $this->hostRegex;
+ }
+
+ /**
+ * Returns the tokens.
+ *
+ * @return array The tokens
+ */
+ public function getTokens()
+ {
+ return $this->tokens;
+ }
+
+ /**
+ * Returns the host tokens.
+ *
+ * @return array The tokens
+ */
+ public function getHostTokens()
+ {
+ return $this->hostTokens;
+ }
+
+ /**
+ * Returns the variables.
+ *
+ * @return array The variables
+ */
+ public function getVariables()
+ {
+ return $this->variables;
+ }
+
+ /**
+ * Returns the path variables.
+ *
+ * @return array The variables
+ */
+ public function getPathVariables()
+ {
+ return $this->pathVariables;
+ }
+
+ /**
+ * Returns the host variables.
+ *
+ * @return array The variables
+ */
+ public function getHostVariables()
+ {
+ return $this->hostVariables;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php b/assets/php/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php
new file mode 100644
index 0000000..4af0a5a
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
+
+/**
+ * Adds tagged routing.loader services to routing.resolver service.
+ *
+ * @author Fabien Potencier
+ */
+class RoutingResolverPass implements CompilerPassInterface
+{
+ use PriorityTaggedServiceTrait;
+
+ private $resolverServiceId;
+ private $loaderTag;
+
+ public function __construct($resolverServiceId = 'routing.resolver', $loaderTag = 'routing.loader')
+ {
+ $this->resolverServiceId = $resolverServiceId;
+ $this->loaderTag = $loaderTag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (false === $container->hasDefinition($this->resolverServiceId)) {
+ return;
+ }
+
+ $definition = $container->getDefinition($this->resolverServiceId);
+
+ foreach ($this->findAndSortTaggedServices($this->loaderTag, $container) as $id) {
+ $definition->addMethodCall('addLoader', array(new Reference($id)));
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Exception/ExceptionInterface.php b/assets/php/vendor/symfony/routing/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..db76362
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * ExceptionInterface.
+ *
+ * @author Alexandre Salomé
+ */
+interface ExceptionInterface
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Exception/InvalidParameterException.php b/assets/php/vendor/symfony/routing/Exception/InvalidParameterException.php
new file mode 100644
index 0000000..94d841f
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Exception/InvalidParameterException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * Exception thrown when a parameter is not valid.
+ *
+ * @author Alexandre Salomé
+ */
+class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Exception/MethodNotAllowedException.php b/assets/php/vendor/symfony/routing/Exception/MethodNotAllowedException.php
new file mode 100644
index 0000000..712412f
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Exception/MethodNotAllowedException.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * The resource was found but the request method is not allowed.
+ *
+ * This exception should trigger an HTTP 405 response in your application code.
+ *
+ * @author Kris Wallsmith
+ */
+class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface
+{
+ protected $allowedMethods = array();
+
+ public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null)
+ {
+ $this->allowedMethods = array_map('strtoupper', $allowedMethods);
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * Gets the allowed HTTP methods.
+ *
+ * @return array
+ */
+ public function getAllowedMethods()
+ {
+ return $this->allowedMethods;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php b/assets/php/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php
new file mode 100644
index 0000000..57f3a40
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * Exception thrown when a route cannot be generated because of missing
+ * mandatory parameters.
+ *
+ * @author Alexandre Salomé
+ */
+class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Exception/NoConfigurationException.php b/assets/php/vendor/symfony/routing/Exception/NoConfigurationException.php
new file mode 100644
index 0000000..333bc74
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Exception/NoConfigurationException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * Exception thrown when no routes are configured.
+ *
+ * @author Yonel Ceruto
+ */
+class NoConfigurationException extends ResourceNotFoundException
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Exception/ResourceNotFoundException.php b/assets/php/vendor/symfony/routing/Exception/ResourceNotFoundException.php
new file mode 100644
index 0000000..ccbca15
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Exception/ResourceNotFoundException.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * The resource was not found.
+ *
+ * This exception should trigger an HTTP 404 response in your application code.
+ *
+ * @author Kris Wallsmith
+ */
+class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Exception/RouteNotFoundException.php b/assets/php/vendor/symfony/routing/Exception/RouteNotFoundException.php
new file mode 100644
index 0000000..24ab0b4
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Exception/RouteNotFoundException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Exception;
+
+/**
+ * Exception thrown when a route does not exist.
+ *
+ * @author Alexandre Salomé
+ */
+class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php b/assets/php/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php
new file mode 100644
index 0000000..dc97b7e
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator;
+
+/**
+ * ConfigurableRequirementsInterface must be implemented by URL generators that
+ * can be configured whether an exception should be generated when the parameters
+ * do not match the requirements. It is also possible to disable the requirements
+ * check for URL generation completely.
+ *
+ * The possible configurations and use-cases:
+ * - setStrictRequirements(true): Throw an exception for mismatching requirements. This
+ * is mostly useful in development environment.
+ * - setStrictRequirements(false): Don't throw an exception but return null as URL for
+ * mismatching requirements and log the problem. Useful when you cannot control all
+ * params because they come from third party libs but don't want to have a 404 in
+ * production environment. It should log the mismatch so one can review it.
+ * - setStrictRequirements(null): Return the URL with the given parameters without
+ * checking the requirements at all. When generating a URL you should either trust
+ * your params or you validated them beforehand because otherwise it would break your
+ * link anyway. So in production environment you should know that params always pass
+ * the requirements. Thus this option allows to disable the check on URL generation for
+ * performance reasons (saving a preg_match for each requirement every time a URL is
+ * generated).
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+interface ConfigurableRequirementsInterface
+{
+ /**
+ * Enables or disables the exception on incorrect parameters.
+ * Passing null will deactivate the requirements check completely.
+ *
+ * @param bool|null $enabled
+ */
+ public function setStrictRequirements($enabled);
+
+ /**
+ * Returns whether to throw an exception on incorrect parameters.
+ * Null means the requirements check is deactivated completely.
+ *
+ * @return bool|null
+ */
+ public function isStrictRequirements();
+}
diff --git a/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php b/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php
new file mode 100644
index 0000000..659c5ba
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator\Dumper;
+
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * GeneratorDumper is the base class for all built-in generator dumpers.
+ *
+ * @author Fabien Potencier
+ */
+abstract class GeneratorDumper implements GeneratorDumperInterface
+{
+ private $routes;
+
+ public function __construct(RouteCollection $routes)
+ {
+ $this->routes = $routes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRoutes()
+ {
+ return $this->routes;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php b/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php
new file mode 100644
index 0000000..fed3472
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator\Dumper;
+
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * GeneratorDumperInterface is the interface that all generator dumper classes must implement.
+ *
+ * @author Fabien Potencier
+ */
+interface GeneratorDumperInterface
+{
+ /**
+ * Dumps a set of routes to a string representation of executable code
+ * that can then be used to generate a URL of such a route.
+ *
+ * @param array $options An array of options
+ *
+ * @return string Executable code
+ */
+ public function dump(array $options = array());
+
+ /**
+ * Gets the routes to dump.
+ *
+ * @return RouteCollection A RouteCollection instance
+ */
+ public function getRoutes();
+}
diff --git a/assets/php/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php b/assets/php/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php
new file mode 100644
index 0000000..60bdf1d
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php
@@ -0,0 +1,118 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator\Dumper;
+
+/**
+ * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+class PhpGeneratorDumper extends GeneratorDumper
+{
+ /**
+ * Dumps a set of routes to a PHP class.
+ *
+ * Available options:
+ *
+ * * class: The class name
+ * * base_class: The base class name
+ *
+ * @param array $options An array of options
+ *
+ * @return string A PHP class representing the generator class
+ */
+ public function dump(array $options = array())
+ {
+ $options = array_merge(array(
+ 'class' => 'ProjectUrlGenerator',
+ 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
+ ), $options);
+
+ return <<context = \$context;
+ \$this->logger = \$logger;
+ if (null === self::\$declaredRoutes) {
+ self::\$declaredRoutes = {$this->generateDeclaredRoutes()};
+ }
+ }
+
+{$this->generateGenerateMethod()}
+}
+
+EOF;
+ }
+
+ /**
+ * Generates PHP code representing an array of defined routes
+ * together with the routes properties (e.g. requirements).
+ *
+ * @return string PHP code
+ */
+ private function generateDeclaredRoutes()
+ {
+ $routes = "array(\n";
+ foreach ($this->getRoutes()->all() as $name => $route) {
+ $compiledRoute = $route->compile();
+
+ $properties = array();
+ $properties[] = $compiledRoute->getVariables();
+ $properties[] = $route->getDefaults();
+ $properties[] = $route->getRequirements();
+ $properties[] = $compiledRoute->getTokens();
+ $properties[] = $compiledRoute->getHostTokens();
+ $properties[] = $route->getSchemes();
+
+ $routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true)));
+ }
+ $routes .= ' )';
+
+ return $routes;
+ }
+
+ /**
+ * Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface.
+ *
+ * @return string PHP code
+ */
+ private function generateGenerateMethod()
+ {
+ return <<<'EOF'
+ public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
+ {
+ if (!isset(self::$declaredRoutes[$name])) {
+ throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
+ }
+
+ list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name];
+
+ return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
+ }
+EOF;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Generator/UrlGenerator.php b/assets/php/vendor/symfony/routing/Generator/UrlGenerator.php
new file mode 100644
index 0000000..02a59a9
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Generator/UrlGenerator.php
@@ -0,0 +1,321 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator;
+
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Exception\InvalidParameterException;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
+use Psr\Log\LoggerInterface;
+
+/**
+ * UrlGenerator can generate a URL or a path for any route in the RouteCollection
+ * based on the passed parameters.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface
+{
+ protected $routes;
+ protected $context;
+
+ /**
+ * @var bool|null
+ */
+ protected $strictRequirements = true;
+
+ protected $logger;
+
+ /**
+ * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL.
+ *
+ * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars
+ * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g.
+ * "?" and "#" (would be interpreted wrongly as query and fragment identifier),
+ * "'" and """ (are used as delimiters in HTML).
+ */
+ protected $decodedChars = array(
+ // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning
+ // some webservers don't allow the slash in encoded form in the path for security reasons anyway
+ // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss
+ '%2F' => '/',
+ // the following chars are general delimiters in the URI specification but have only special meaning in the authority component
+ // so they can safely be used in the path in unencoded form
+ '%40' => '@',
+ '%3A' => ':',
+ // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally
+ // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability
+ '%3B' => ';',
+ '%2C' => ',',
+ '%3D' => '=',
+ '%2B' => '+',
+ '%21' => '!',
+ '%2A' => '*',
+ '%7C' => '|',
+ );
+
+ public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null)
+ {
+ $this->routes = $routes;
+ $this->context = $context;
+ $this->logger = $logger;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setContext(RequestContext $context)
+ {
+ $this->context = $context;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setStrictRequirements($enabled)
+ {
+ $this->strictRequirements = null === $enabled ? null : (bool) $enabled;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isStrictRequirements()
+ {
+ return $this->strictRequirements;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
+ {
+ if (null === $route = $this->routes->get($name)) {
+ throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
+ }
+
+ // the Route has a cache of its own and is not recompiled as long as it does not get modified
+ $compiledRoute = $route->compile();
+
+ return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes());
+ }
+
+ /**
+ * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route
+ * @throws InvalidParameterException When a parameter value for a placeholder is not correct because
+ * it does not match the requirement
+ */
+ protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array())
+ {
+ $variables = array_flip($variables);
+ $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters);
+
+ // all params must be given
+ if ($diff = array_diff_key($variables, $mergedParams)) {
+ throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name));
+ }
+
+ $url = '';
+ $optional = true;
+ $message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.';
+ foreach ($tokens as $token) {
+ if ('variable' === $token[0]) {
+ if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) {
+ // check requirement
+ if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) {
+ if ($this->strictRequirements) {
+ throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]])));
+ }
+
+ if ($this->logger) {
+ $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]));
+ }
+
+ return;
+ }
+
+ $url = $token[1].$mergedParams[$token[3]].$url;
+ $optional = false;
+ }
+ } else {
+ // static text
+ $url = $token[1].$url;
+ $optional = false;
+ }
+ }
+
+ if ('' === $url) {
+ $url = '/';
+ }
+
+ // the contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request)
+ $url = strtr(rawurlencode($url), $this->decodedChars);
+
+ // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3
+ // so we need to encode them as they are not used for this purpose here
+ // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route
+ $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/'));
+ if ('/..' === substr($url, -3)) {
+ $url = substr($url, 0, -2).'%2E%2E';
+ } elseif ('/.' === substr($url, -2)) {
+ $url = substr($url, 0, -1).'%2E';
+ }
+
+ $schemeAuthority = '';
+ $host = $this->context->getHost();
+ $scheme = $this->context->getScheme();
+
+ if ($requiredSchemes) {
+ if (!in_array($scheme, $requiredSchemes, true)) {
+ $referenceType = self::ABSOLUTE_URL;
+ $scheme = current($requiredSchemes);
+ }
+ }
+
+ if ($hostTokens) {
+ $routeHost = '';
+ foreach ($hostTokens as $token) {
+ if ('variable' === $token[0]) {
+ if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) {
+ if ($this->strictRequirements) {
+ throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]])));
+ }
+
+ if ($this->logger) {
+ $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]));
+ }
+
+ return;
+ }
+
+ $routeHost = $token[1].$mergedParams[$token[3]].$routeHost;
+ } else {
+ $routeHost = $token[1].$routeHost;
+ }
+ }
+
+ if ($routeHost !== $host) {
+ $host = $routeHost;
+ if (self::ABSOLUTE_URL !== $referenceType) {
+ $referenceType = self::NETWORK_PATH;
+ }
+ }
+ }
+
+ if ((self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) && !empty($host)) {
+ $port = '';
+ if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
+ $port = ':'.$this->context->getHttpPort();
+ } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) {
+ $port = ':'.$this->context->getHttpsPort();
+ }
+
+ $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://";
+ $schemeAuthority .= $host.$port;
+ }
+
+ if (self::RELATIVE_PATH === $referenceType) {
+ $url = self::getRelativePath($this->context->getPathInfo(), $url);
+ } else {
+ $url = $schemeAuthority.$this->context->getBaseUrl().$url;
+ }
+
+ // add a query string if needed
+ $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, function ($a, $b) {
+ return $a == $b ? 0 : 1;
+ });
+
+ // extract fragment
+ $fragment = '';
+ if (isset($defaults['_fragment'])) {
+ $fragment = $defaults['_fragment'];
+ }
+
+ if (isset($extra['_fragment'])) {
+ $fragment = $extra['_fragment'];
+ unset($extra['_fragment']);
+ }
+
+ if ($extra && $query = http_build_query($extra, '', '&', PHP_QUERY_RFC3986)) {
+ // "/" and "?" can be left decoded for better user experience, see
+ // http://tools.ietf.org/html/rfc3986#section-3.4
+ $url .= '?'.strtr($query, array('%2F' => '/'));
+ }
+
+ if ('' !== $fragment) {
+ $url .= '#'.strtr(rawurlencode($fragment), array('%2F' => '/', '%3F' => '?'));
+ }
+
+ return $url;
+ }
+
+ /**
+ * Returns the target path as relative reference from the base path.
+ *
+ * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash.
+ * Both paths must be absolute and not contain relative parts.
+ * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
+ * Furthermore, they can be used to reduce the link size in documents.
+ *
+ * Example target paths, given a base path of "/a/b/c/d":
+ * - "/a/b/c/d" -> ""
+ * - "/a/b/c/" -> "./"
+ * - "/a/b/" -> "../"
+ * - "/a/b/c/other" -> "other"
+ * - "/a/x/y" -> "../../x/y"
+ *
+ * @param string $basePath The base path
+ * @param string $targetPath The target path
+ *
+ * @return string The relative target path
+ */
+ public static function getRelativePath($basePath, $targetPath)
+ {
+ if ($basePath === $targetPath) {
+ return '';
+ }
+
+ $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
+ $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath);
+ array_pop($sourceDirs);
+ $targetFile = array_pop($targetDirs);
+
+ foreach ($sourceDirs as $i => $dir) {
+ if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
+ unset($sourceDirs[$i], $targetDirs[$i]);
+ } else {
+ break;
+ }
+ }
+
+ $targetDirs[] = $targetFile;
+ $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs);
+
+ // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
+ // (see http://tools.ietf.org/html/rfc3986#section-4.2).
+ return '' === $path || '/' === $path[0]
+ || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
+ ? "./$path" : $path;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Generator/UrlGeneratorInterface.php b/assets/php/vendor/symfony/routing/Generator/UrlGeneratorInterface.php
new file mode 100644
index 0000000..d6e7938
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Generator/UrlGeneratorInterface.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator;
+
+use Symfony\Component\Routing\Exception\InvalidParameterException;
+use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\RequestContextAwareInterface;
+
+/**
+ * UrlGeneratorInterface is the interface that all URL generator classes must implement.
+ *
+ * The constants in this interface define the different types of resource references that
+ * are declared in RFC 3986: http://tools.ietf.org/html/rfc3986
+ * We are using the term "URL" instead of "URI" as this is more common in web applications
+ * and we do not need to distinguish them as the difference is mostly semantical and
+ * less technical. Generating URIs, i.e. representation-independent resource identifiers,
+ * is also possible.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+interface UrlGeneratorInterface extends RequestContextAwareInterface
+{
+ /**
+ * Generates an absolute URL, e.g. "http://example.com/dir/file".
+ */
+ const ABSOLUTE_URL = 0;
+
+ /**
+ * Generates an absolute path, e.g. "/dir/file".
+ */
+ const ABSOLUTE_PATH = 1;
+
+ /**
+ * Generates a relative path based on the current request path, e.g. "../parent-file".
+ *
+ * @see UrlGenerator::getRelativePath()
+ */
+ const RELATIVE_PATH = 2;
+
+ /**
+ * Generates a network path, e.g. "//example.com/dir/file".
+ * Such reference reuses the current scheme but specifies the host.
+ */
+ const NETWORK_PATH = 3;
+
+ /**
+ * Generates a URL or path for a specific route based on the given parameters.
+ *
+ * Parameters that reference placeholders in the route pattern will substitute them in the
+ * path or host. Extra params are added as query string to the URL.
+ *
+ * When the passed reference type cannot be generated for the route because it requires a different
+ * host or scheme than the current one, the method will return a more comprehensive reference
+ * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH
+ * but the route requires the https scheme whereas the current scheme is http, it will instead return an
+ * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches
+ * the route in any case.
+ *
+ * If there is no route with the given name, the generator must throw the RouteNotFoundException.
+ *
+ * The special parameter _fragment will be used as the document fragment suffixed to the final URL.
+ *
+ * @param string $name The name of the route
+ * @param mixed $parameters An array of parameters
+ * @param int $referenceType The type of reference to be generated (one of the constants)
+ *
+ * @return string The generated URL
+ *
+ * @throws RouteNotFoundException If the named route doesn't exist
+ * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route
+ * @throws InvalidParameterException When a parameter value for a placeholder is not correct because
+ * it does not match the requirement
+ */
+ public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH);
+}
diff --git a/assets/php/vendor/symfony/routing/LICENSE b/assets/php/vendor/symfony/routing/LICENSE
new file mode 100644
index 0000000..21d7fb9
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2018 Fabien Potencier
+
+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.
diff --git a/assets/php/vendor/symfony/routing/Loader/AnnotationClassLoader.php b/assets/php/vendor/symfony/routing/Loader/AnnotationClassLoader.php
new file mode 100644
index 0000000..2fe6fb5
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/AnnotationClassLoader.php
@@ -0,0 +1,269 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Doctrine\Common\Annotations\Reader;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Config\Loader\LoaderInterface;
+use Symfony\Component\Config\Loader\LoaderResolverInterface;
+
+/**
+ * AnnotationClassLoader loads routing information from a PHP class and its methods.
+ *
+ * You need to define an implementation for the getRouteDefaults() method. Most of the
+ * time, this method should define some PHP callable to be called for the route
+ * (a controller in MVC speak).
+ *
+ * The @Route annotation can be set on the class (for global parameters),
+ * and on each method.
+ *
+ * The @Route annotation main value is the route path. The annotation also
+ * recognizes several parameters: requirements, options, defaults, schemes,
+ * methods, host, and name. The name parameter is mandatory.
+ * Here is an example of how you should be able to use it:
+ *
+ * /**
+ * * @Route("/Blog")
+ * * /
+ * class Blog
+ * {
+ * /**
+ * * @Route("/", name="blog_index")
+ * * /
+ * public function index()
+ * {
+ * }
+ *
+ * /**
+ * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"})
+ * * /
+ * public function show()
+ * {
+ * }
+ * }
+ *
+ * @author Fabien Potencier
+ */
+abstract class AnnotationClassLoader implements LoaderInterface
+{
+ protected $reader;
+
+ /**
+ * @var string
+ */
+ protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route';
+
+ /**
+ * @var int
+ */
+ protected $defaultRouteIndex = 0;
+
+ public function __construct(Reader $reader)
+ {
+ $this->reader = $reader;
+ }
+
+ /**
+ * Sets the annotation class to read route properties from.
+ *
+ * @param string $class A fully-qualified class name
+ */
+ public function setRouteAnnotationClass($class)
+ {
+ $this->routeAnnotationClass = $class;
+ }
+
+ /**
+ * Loads from annotations from a class.
+ *
+ * @param string $class A class name
+ * @param string|null $type The resource type
+ *
+ * @return RouteCollection A RouteCollection instance
+ *
+ * @throws \InvalidArgumentException When route can't be parsed
+ */
+ public function load($class, $type = null)
+ {
+ if (!class_exists($class)) {
+ throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
+ }
+
+ $class = new \ReflectionClass($class);
+ if ($class->isAbstract()) {
+ throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName()));
+ }
+
+ $globals = $this->getGlobals($class);
+
+ $collection = new RouteCollection();
+ $collection->addResource(new FileResource($class->getFileName()));
+
+ foreach ($class->getMethods() as $method) {
+ $this->defaultRouteIndex = 0;
+ foreach ($this->reader->getMethodAnnotations($method) as $annot) {
+ if ($annot instanceof $this->routeAnnotationClass) {
+ $this->addRoute($collection, $annot, $globals, $class, $method);
+ }
+ }
+ }
+
+ if (0 === $collection->count() && $class->hasMethod('__invoke') && $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) {
+ $globals['path'] = '';
+ $globals['name'] = '';
+ $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
+ }
+
+ return $collection;
+ }
+
+ protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method)
+ {
+ $name = $annot->getName();
+ if (null === $name) {
+ $name = $this->getDefaultRouteName($class, $method);
+ }
+ $name = $globals['name'].$name;
+
+ $defaults = array_replace($globals['defaults'], $annot->getDefaults());
+ foreach ($method->getParameters() as $param) {
+ if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) {
+ $defaults[$param->getName()] = $param->getDefaultValue();
+ }
+ }
+ $requirements = array_replace($globals['requirements'], $annot->getRequirements());
+ $options = array_replace($globals['options'], $annot->getOptions());
+ $schemes = array_merge($globals['schemes'], $annot->getSchemes());
+ $methods = array_merge($globals['methods'], $annot->getMethods());
+
+ $host = $annot->getHost();
+ if (null === $host) {
+ $host = $globals['host'];
+ }
+
+ $condition = $annot->getCondition();
+ if (null === $condition) {
+ $condition = $globals['condition'];
+ }
+
+ $route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
+
+ $this->configureRoute($route, $class, $method, $annot);
+
+ $collection->add($name, $route);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setResolver(LoaderResolverInterface $resolver)
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getResolver()
+ {
+ }
+
+ /**
+ * Gets the default route name for a class method.
+ *
+ * @param \ReflectionClass $class
+ * @param \ReflectionMethod $method
+ *
+ * @return string
+ */
+ protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
+ {
+ $name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name);
+ if ($this->defaultRouteIndex > 0) {
+ $name .= '_'.$this->defaultRouteIndex;
+ }
+ ++$this->defaultRouteIndex;
+
+ return $name;
+ }
+
+ protected function getGlobals(\ReflectionClass $class)
+ {
+ $globals = array(
+ 'path' => '',
+ 'requirements' => array(),
+ 'options' => array(),
+ 'defaults' => array(),
+ 'schemes' => array(),
+ 'methods' => array(),
+ 'host' => '',
+ 'condition' => '',
+ 'name' => '',
+ );
+
+ if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) {
+ if (null !== $annot->getName()) {
+ $globals['name'] = $annot->getName();
+ }
+
+ if (null !== $annot->getPath()) {
+ $globals['path'] = $annot->getPath();
+ }
+
+ if (null !== $annot->getRequirements()) {
+ $globals['requirements'] = $annot->getRequirements();
+ }
+
+ if (null !== $annot->getOptions()) {
+ $globals['options'] = $annot->getOptions();
+ }
+
+ if (null !== $annot->getDefaults()) {
+ $globals['defaults'] = $annot->getDefaults();
+ }
+
+ if (null !== $annot->getSchemes()) {
+ $globals['schemes'] = $annot->getSchemes();
+ }
+
+ if (null !== $annot->getMethods()) {
+ $globals['methods'] = $annot->getMethods();
+ }
+
+ if (null !== $annot->getHost()) {
+ $globals['host'] = $annot->getHost();
+ }
+
+ if (null !== $annot->getCondition()) {
+ $globals['condition'] = $annot->getCondition();
+ }
+ }
+
+ return $globals;
+ }
+
+ protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition)
+ {
+ return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
+ }
+
+ abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot);
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php b/assets/php/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php
new file mode 100644
index 0000000..4574a02
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Config\Resource\DirectoryResource;
+
+/**
+ * AnnotationDirectoryLoader loads routing information from annotations set
+ * on PHP classes and methods.
+ *
+ * @author Fabien Potencier
+ */
+class AnnotationDirectoryLoader extends AnnotationFileLoader
+{
+ /**
+ * Loads from annotations from a directory.
+ *
+ * @param string $path A directory path
+ * @param string|null $type The resource type
+ *
+ * @return RouteCollection A RouteCollection instance
+ *
+ * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed
+ */
+ public function load($path, $type = null)
+ {
+ if (!is_dir($dir = $this->locator->locate($path))) {
+ return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection();
+ }
+
+ $collection = new RouteCollection();
+ $collection->addResource(new DirectoryResource($dir, '/\.php$/'));
+ $files = iterator_to_array(new \RecursiveIteratorIterator(
+ new \RecursiveCallbackFilterIterator(
+ new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
+ function (\SplFileInfo $current) {
+ return '.' !== substr($current->getBasename(), 0, 1);
+ }
+ ),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ ));
+ usort($files, function (\SplFileInfo $a, \SplFileInfo $b) {
+ return (string) $a > (string) $b ? 1 : -1;
+ });
+
+ foreach ($files as $file) {
+ if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) {
+ continue;
+ }
+
+ if ($class = $this->findClass($file)) {
+ $refl = new \ReflectionClass($class);
+ if ($refl->isAbstract()) {
+ continue;
+ }
+
+ $collection->addCollection($this->loader->load($class, $type));
+ }
+ }
+
+ return $collection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ if ('annotation' === $type) {
+ return true;
+ }
+
+ if ($type || !is_string($resource)) {
+ return false;
+ }
+
+ try {
+ return is_dir($this->locator->locate($resource));
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/AnnotationFileLoader.php b/assets/php/vendor/symfony/routing/Loader/AnnotationFileLoader.php
new file mode 100644
index 0000000..cf9f070
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/AnnotationFileLoader.php
@@ -0,0 +1,142 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Config\Loader\FileLoader;
+use Symfony\Component\Config\FileLocatorInterface;
+
+/**
+ * AnnotationFileLoader loads routing information from annotations set
+ * on a PHP class and its methods.
+ *
+ * @author Fabien Potencier
+ */
+class AnnotationFileLoader extends FileLoader
+{
+ protected $loader;
+
+ /**
+ * @throws \RuntimeException
+ */
+ public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader)
+ {
+ if (!function_exists('token_get_all')) {
+ throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.');
+ }
+
+ parent::__construct($locator);
+
+ $this->loader = $loader;
+ }
+
+ /**
+ * Loads from annotations from a file.
+ *
+ * @param string $file A PHP file path
+ * @param string|null $type The resource type
+ *
+ * @return RouteCollection A RouteCollection instance
+ *
+ * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed
+ */
+ public function load($file, $type = null)
+ {
+ $path = $this->locator->locate($file);
+
+ $collection = new RouteCollection();
+ if ($class = $this->findClass($path)) {
+ $collection->addResource(new FileResource($path));
+ $collection->addCollection($this->loader->load($class, $type));
+ }
+ if (\PHP_VERSION_ID >= 70000) {
+ // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098
+ gc_mem_caches();
+ }
+
+ return $collection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type);
+ }
+
+ /**
+ * Returns the full class name for the first class in the file.
+ *
+ * @param string $file A PHP file path
+ *
+ * @return string|false Full class name if found, false otherwise
+ */
+ protected function findClass($file)
+ {
+ $class = false;
+ $namespace = false;
+ $tokens = token_get_all(file_get_contents($file));
+
+ if (1 === count($tokens) && T_INLINE_HTML === $tokens[0][0]) {
+ throw new \InvalidArgumentException(sprintf('The file "%s" does not contain PHP code. Did you forgot to add the " 0; --$j) {
+ if (!isset($tokens[$j][1])) {
+ break;
+ }
+
+ if (T_DOUBLE_COLON === $tokens[$j][0] || T_NEW === $tokens[$j][0]) {
+ $skipClassToken = true;
+ break;
+ } elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) {
+ break;
+ }
+ }
+
+ if (!$skipClassToken) {
+ $class = true;
+ }
+ }
+
+ if (T_NAMESPACE === $token[0]) {
+ $namespace = true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/ClosureLoader.php b/assets/php/vendor/symfony/routing/Loader/ClosureLoader.php
new file mode 100644
index 0000000..5df9f6a
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/ClosureLoader.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Config\Loader\Loader;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * ClosureLoader loads routes from a PHP closure.
+ *
+ * The Closure must return a RouteCollection instance.
+ *
+ * @author Fabien Potencier
+ */
+class ClosureLoader extends Loader
+{
+ /**
+ * Loads a Closure.
+ *
+ * @param \Closure $closure A Closure
+ * @param string|null $type The resource type
+ *
+ * @return RouteCollection A RouteCollection instance
+ */
+ public function load($closure, $type = null)
+ {
+ return $closure();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return $resource instanceof \Closure && (!$type || 'closure' === $type);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php b/assets/php/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php
new file mode 100644
index 0000000..8baefdd
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader\Configurator;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * @author Nicolas Grekas
+ */
+class CollectionConfigurator
+{
+ use Traits\AddTrait;
+ use Traits\RouteTrait;
+
+ private $parent;
+ private $parentConfigurator;
+
+ public function __construct(RouteCollection $parent, $name, self $parentConfigurator = null)
+ {
+ $this->parent = $parent;
+ $this->name = $name;
+ $this->collection = new RouteCollection();
+ $this->route = new Route('');
+ $this->parentConfigurator = $parentConfigurator; // for GC control
+ }
+
+ public function __destruct()
+ {
+ $this->collection->addPrefix(rtrim($this->route->getPath(), '/'));
+ $this->parent->addCollection($this->collection);
+ }
+
+ /**
+ * Adds a route.
+ *
+ * @param string $name
+ * @param string $path
+ *
+ * @return RouteConfigurator
+ */
+ final public function add($name, $path)
+ {
+ $this->collection->add($this->name.$name, $route = clone $this->route);
+
+ return new RouteConfigurator($this->collection, $route->setPath($path), $this->name, $this);
+ }
+
+ /**
+ * Creates a sub-collection.
+ *
+ * @return self
+ */
+ final public function collection($name = '')
+ {
+ return new self($this->collection, $this->name.$name, $this);
+ }
+
+ /**
+ * Sets the prefix to add to the path of all child routes.
+ *
+ * @param string $prefix
+ *
+ * @return $this
+ */
+ final public function prefix($prefix)
+ {
+ $this->route->setPath($prefix);
+
+ return $this;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php b/assets/php/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php
new file mode 100644
index 0000000..d0a3c37
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader\Configurator;
+
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ImportConfigurator
+{
+ use Traits\RouteTrait;
+
+ private $parent;
+
+ public function __construct(RouteCollection $parent, RouteCollection $route)
+ {
+ $this->parent = $parent;
+ $this->route = $route;
+ }
+
+ public function __destruct()
+ {
+ $this->parent->addCollection($this->route);
+ }
+
+ /**
+ * Sets the prefix to add to the path of all child routes.
+ *
+ * @param string $prefix
+ *
+ * @return $this
+ */
+ final public function prefix($prefix)
+ {
+ $this->route->addPrefix($prefix);
+
+ return $this;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php b/assets/php/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php
new file mode 100644
index 0000000..6422bbf
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader\Configurator;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * @author Nicolas Grekas
+ */
+class RouteConfigurator
+{
+ use Traits\AddTrait;
+ use Traits\RouteTrait;
+
+ private $parentConfigurator;
+
+ public function __construct(RouteCollection $collection, Route $route, $name = '', CollectionConfigurator $parentConfigurator = null)
+ {
+ $this->collection = $collection;
+ $this->route = $route;
+ $this->name = $name;
+ $this->parentConfigurator = $parentConfigurator; // for GC control
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php b/assets/php/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php
new file mode 100644
index 0000000..d992cef
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader\Configurator;
+
+use Symfony\Component\Routing\Loader\PhpFileLoader;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * @author Nicolas Grekas
+ */
+class RoutingConfigurator
+{
+ use Traits\AddTrait;
+
+ private $loader;
+ private $path;
+ private $file;
+
+ public function __construct(RouteCollection $collection, PhpFileLoader $loader, $path, $file)
+ {
+ $this->collection = $collection;
+ $this->loader = $loader;
+ $this->path = $path;
+ $this->file = $file;
+ }
+
+ /**
+ * @return ImportConfigurator
+ */
+ final public function import($resource, $type = null, $ignoreErrors = false)
+ {
+ $this->loader->setCurrentDir(dirname($this->path));
+ $imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file);
+ if (!is_array($imported)) {
+ return new ImportConfigurator($this->collection, $imported);
+ }
+
+ $mergedCollection = new RouteCollection();
+ foreach ($imported as $subCollection) {
+ $mergedCollection->addCollection($subCollection);
+ }
+
+ return new ImportConfigurator($this->collection, $mergedCollection);
+ }
+
+ /**
+ * @return CollectionConfigurator
+ */
+ final public function collection($name = '')
+ {
+ return new CollectionConfigurator($this->collection, $name);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php b/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php
new file mode 100644
index 0000000..e8b8fa2
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader\Configurator\Traits;
+
+use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+trait AddTrait
+{
+ /**
+ * @var RouteCollection
+ */
+ private $collection;
+
+ private $name = '';
+
+ /**
+ * Adds a route.
+ *
+ * @param string $name
+ * @param string $path
+ *
+ * @return RouteConfigurator
+ */
+ final public function add($name, $path)
+ {
+ $parentConfigurator = $this instanceof RouteConfigurator ? $this->parentConfigurator : null;
+ $this->collection->add($this->name.$name, $route = new Route($path));
+
+ return new RouteConfigurator($this->collection, $route, '', $parentConfigurator);
+ }
+
+ /**
+ * Adds a route.
+ *
+ * @param string $name
+ * @param string $path
+ *
+ * @return RouteConfigurator
+ */
+ final public function __invoke($name, $path)
+ {
+ return $this->add($name, $path);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php b/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php
new file mode 100644
index 0000000..4d2e255
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php
@@ -0,0 +1,131 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader\Configurator\Traits;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+trait RouteTrait
+{
+ /**
+ * @var RouteCollection|Route
+ */
+ private $route;
+
+ /**
+ * Adds defaults.
+ *
+ * @return $this
+ */
+ final public function defaults(array $defaults)
+ {
+ $this->route->addDefaults($defaults);
+
+ return $this;
+ }
+
+ /**
+ * Adds requirements.
+ *
+ * @return $this
+ */
+ final public function requirements(array $requirements)
+ {
+ $this->route->addRequirements($requirements);
+
+ return $this;
+ }
+
+ /**
+ * Adds options.
+ *
+ * @return $this
+ */
+ final public function options(array $options)
+ {
+ $this->route->addOptions($options);
+
+ return $this;
+ }
+
+ /**
+ * Sets the condition.
+ *
+ * @param string $condition
+ *
+ * @return $this
+ */
+ final public function condition($condition)
+ {
+ $this->route->setCondition($condition);
+
+ return $this;
+ }
+
+ /**
+ * Sets the pattern for the host.
+ *
+ * @param string $pattern
+ *
+ * @return $this
+ */
+ final public function host($pattern)
+ {
+ $this->route->setHost($pattern);
+
+ return $this;
+ }
+
+ /**
+ * Sets the schemes (e.g. 'https') this route is restricted to.
+ * So an empty array means that any scheme is allowed.
+ *
+ * @param string[] $schemes
+ *
+ * @return $this
+ */
+ final public function schemes(array $schemes)
+ {
+ $this->route->setSchemes($schemes);
+
+ return $this;
+ }
+
+ /**
+ * Sets the HTTP methods (e.g. 'POST') this route is restricted to.
+ * So an empty array means that any method is allowed.
+ *
+ * @param string[] $methods
+ *
+ * @return $this
+ */
+ final public function methods(array $methods)
+ {
+ $this->route->setMethods($methods);
+
+ return $this;
+ }
+
+ /**
+ * Adds the "_controller" entry to defaults.
+ *
+ * @param callable|string $controller a callable or parseable pseudo-callable
+ *
+ * @return $this
+ */
+ final public function controller($controller)
+ {
+ $this->route->addDefaults(array('_controller' => $controller));
+
+ return $this;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php b/assets/php/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php
new file mode 100644
index 0000000..6c16216
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader\DependencyInjection;
+
+use Psr\Container\ContainerInterface;
+use Symfony\Component\Routing\Loader\ObjectRouteLoader;
+
+/**
+ * A route loader that executes a service to load the routes.
+ *
+ * This depends on the DependencyInjection component.
+ *
+ * @author Ryan Weaver
+ */
+class ServiceRouterLoader extends ObjectRouteLoader
+{
+ /**
+ * @var ContainerInterface
+ */
+ private $container;
+
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ protected function getServiceObject($id)
+ {
+ return $this->container->get($id);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/DirectoryLoader.php b/assets/php/vendor/symfony/routing/Loader/DirectoryLoader.php
new file mode 100644
index 0000000..4bb5b31
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/DirectoryLoader.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Config\Loader\FileLoader;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Config\Resource\DirectoryResource;
+
+class DirectoryLoader extends FileLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($file, $type = null)
+ {
+ $path = $this->locator->locate($file);
+
+ $collection = new RouteCollection();
+ $collection->addResource(new DirectoryResource($path));
+
+ foreach (scandir($path) as $dir) {
+ if ('.' !== $dir[0]) {
+ $this->setCurrentDir($path);
+ $subPath = $path.'/'.$dir;
+ $subType = null;
+
+ if (is_dir($subPath)) {
+ $subPath .= '/';
+ $subType = 'directory';
+ }
+
+ $subCollection = $this->import($subPath, $subType, false, $path);
+ $collection->addCollection($subCollection);
+ }
+ }
+
+ return $collection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ // only when type is forced to directory, not to conflict with AnnotationLoader
+
+ return 'directory' === $type;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/GlobFileLoader.php b/assets/php/vendor/symfony/routing/Loader/GlobFileLoader.php
new file mode 100644
index 0000000..03ee341
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/GlobFileLoader.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Config\Loader\FileLoader;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * GlobFileLoader loads files from a glob pattern.
+ *
+ * @author Nicolas Grekas
+ */
+class GlobFileLoader extends FileLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $type = null)
+ {
+ $collection = new RouteCollection();
+
+ foreach ($this->glob($resource, false, $globResource) as $path => $info) {
+ $collection->addCollection($this->import($path));
+ }
+
+ $collection->addResource($globResource);
+
+ return $collection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return 'glob' === $type;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/ObjectRouteLoader.php b/assets/php/vendor/symfony/routing/Loader/ObjectRouteLoader.php
new file mode 100644
index 0000000..0899a81
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/ObjectRouteLoader.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Config\Loader\Loader;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * A route loader that calls a method on an object to load the routes.
+ *
+ * @author Ryan Weaver
+ */
+abstract class ObjectRouteLoader extends Loader
+{
+ /**
+ * Returns the object that the method will be called on to load routes.
+ *
+ * For example, if your application uses a service container,
+ * the $id may be a service id.
+ *
+ * @param string $id
+ *
+ * @return object
+ */
+ abstract protected function getServiceObject($id);
+
+ /**
+ * Calls the service that will load the routes.
+ *
+ * @param mixed $resource Some value that will resolve to a callable
+ * @param string|null $type The resource type
+ *
+ * @return RouteCollection
+ */
+ public function load($resource, $type = null)
+ {
+ $parts = explode(':', $resource);
+ if (2 != count($parts)) {
+ throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName"', $resource));
+ }
+
+ $serviceString = $parts[0];
+ $method = $parts[1];
+
+ $loaderObject = $this->getServiceObject($serviceString);
+
+ if (!is_object($loaderObject)) {
+ throw new \LogicException(sprintf('%s:getServiceObject() must return an object: %s returned', get_class($this), gettype($loaderObject)));
+ }
+
+ if (!method_exists($loaderObject, $method)) {
+ throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s"', $method, get_class($loaderObject), $resource));
+ }
+
+ $routeCollection = call_user_func(array($loaderObject, $method), $this);
+
+ if (!$routeCollection instanceof RouteCollection) {
+ $type = is_object($routeCollection) ? get_class($routeCollection) : gettype($routeCollection);
+
+ throw new \LogicException(sprintf('The %s::%s method must return a RouteCollection: %s returned', get_class($loaderObject), $method, $type));
+ }
+
+ // make the service file tracked so that if it changes, the cache rebuilds
+ $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection);
+
+ return $routeCollection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return 'service' === $type;
+ }
+
+ private function addClassResource(\ReflectionClass $class, RouteCollection $collection)
+ {
+ do {
+ if (is_file($class->getFileName())) {
+ $collection->addResource(new FileResource($class->getFileName()));
+ }
+ } while ($class = $class->getParentClass());
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/PhpFileLoader.php b/assets/php/vendor/symfony/routing/Loader/PhpFileLoader.php
new file mode 100644
index 0000000..3fcd692
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/PhpFileLoader.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Config\Loader\FileLoader;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * PhpFileLoader loads routes from a PHP file.
+ *
+ * The file must return a RouteCollection instance.
+ *
+ * @author Fabien Potencier
+ */
+class PhpFileLoader extends FileLoader
+{
+ /**
+ * Loads a PHP file.
+ *
+ * @param string $file A PHP file path
+ * @param string|null $type The resource type
+ *
+ * @return RouteCollection A RouteCollection instance
+ */
+ public function load($file, $type = null)
+ {
+ $path = $this->locator->locate($file);
+ $this->setCurrentDir(dirname($path));
+
+ // the closure forbids access to the private scope in the included file
+ $loader = $this;
+ $load = \Closure::bind(function ($file) use ($loader) {
+ return include $file;
+ }, null, ProtectedPhpFileLoader::class);
+
+ $result = $load($path);
+
+ if ($result instanceof \Closure) {
+ $collection = new RouteCollection();
+ $result(new RoutingConfigurator($collection, $this, $path, $file), $this);
+ } else {
+ $collection = $result;
+ }
+
+ $collection->addResource(new FileResource($path));
+
+ return $collection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type);
+ }
+}
+
+/**
+ * @internal
+ */
+final class ProtectedPhpFileLoader extends PhpFileLoader
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/XmlFileLoader.php b/assets/php/vendor/symfony/routing/Loader/XmlFileLoader.php
new file mode 100644
index 0000000..f3f6605
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/XmlFileLoader.php
@@ -0,0 +1,359 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Config\Loader\FileLoader;
+use Symfony\Component\Config\Util\XmlUtils;
+
+/**
+ * XmlFileLoader loads XML routing files.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+class XmlFileLoader extends FileLoader
+{
+ const NAMESPACE_URI = 'http://symfony.com/schema/routing';
+ const SCHEME_PATH = '/schema/routing/routing-1.0.xsd';
+
+ /**
+ * Loads an XML file.
+ *
+ * @param string $file An XML file path
+ * @param string|null $type The resource type
+ *
+ * @return RouteCollection A RouteCollection instance
+ *
+ * @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be
+ * parsed because it does not validate against the scheme
+ */
+ public function load($file, $type = null)
+ {
+ $path = $this->locator->locate($file);
+
+ $xml = $this->loadFile($path);
+
+ $collection = new RouteCollection();
+ $collection->addResource(new FileResource($path));
+
+ // process routes and imports
+ foreach ($xml->documentElement->childNodes as $node) {
+ if (!$node instanceof \DOMElement) {
+ continue;
+ }
+
+ $this->parseNode($collection, $node, $path, $file);
+ }
+
+ return $collection;
+ }
+
+ /**
+ * Parses a node from a loaded XML file.
+ *
+ * @param RouteCollection $collection Collection to associate with the node
+ * @param \DOMElement $node Element to parse
+ * @param string $path Full path of the XML file being processed
+ * @param string $file Loaded file name
+ *
+ * @throws \InvalidArgumentException When the XML is invalid
+ */
+ protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file)
+ {
+ if (self::NAMESPACE_URI !== $node->namespaceURI) {
+ return;
+ }
+
+ switch ($node->localName) {
+ case 'route':
+ $this->parseRoute($collection, $node, $path);
+ break;
+ case 'import':
+ $this->parseImport($collection, $node, $path, $file);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type);
+ }
+
+ /**
+ * Parses a route and adds it to the RouteCollection.
+ *
+ * @param RouteCollection $collection RouteCollection instance
+ * @param \DOMElement $node Element to parse that represents a Route
+ * @param string $path Full path of the XML file being processed
+ *
+ * @throws \InvalidArgumentException When the XML is invalid
+ */
+ protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path)
+ {
+ if ('' === ($id = $node->getAttribute('id')) || !$node->hasAttribute('path')) {
+ throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" and a "path" attribute.', $path));
+ }
+
+ $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY);
+ $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY);
+
+ list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
+
+ $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
+ $collection->add($id, $route);
+ }
+
+ /**
+ * Parses an import and adds the routes in the resource to the RouteCollection.
+ *
+ * @param RouteCollection $collection RouteCollection instance
+ * @param \DOMElement $node Element to parse that represents a Route
+ * @param string $path Full path of the XML file being processed
+ * @param string $file Loaded file name
+ *
+ * @throws \InvalidArgumentException When the XML is invalid
+ */
+ protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file)
+ {
+ if ('' === $resource = $node->getAttribute('resource')) {
+ throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path));
+ }
+
+ $type = $node->getAttribute('type');
+ $prefix = $node->getAttribute('prefix');
+ $host = $node->hasAttribute('host') ? $node->getAttribute('host') : null;
+ $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
+ $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
+
+ list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
+
+ $this->setCurrentDir(dirname($path));
+
+ $imported = $this->import($resource, ('' !== $type ? $type : null), false, $file);
+
+ if (!is_array($imported)) {
+ $imported = array($imported);
+ }
+
+ foreach ($imported as $subCollection) {
+ /* @var $subCollection RouteCollection */
+ $subCollection->addPrefix($prefix);
+ if (null !== $host) {
+ $subCollection->setHost($host);
+ }
+ if (null !== $condition) {
+ $subCollection->setCondition($condition);
+ }
+ if (null !== $schemes) {
+ $subCollection->setSchemes($schemes);
+ }
+ if (null !== $methods) {
+ $subCollection->setMethods($methods);
+ }
+ $subCollection->addDefaults($defaults);
+ $subCollection->addRequirements($requirements);
+ $subCollection->addOptions($options);
+
+ $collection->addCollection($subCollection);
+ }
+ }
+
+ /**
+ * Loads an XML file.
+ *
+ * @param string $file An XML file path
+ *
+ * @return \DOMDocument
+ *
+ * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors
+ * or when the XML structure is not as expected by the scheme -
+ * see validate()
+ */
+ protected function loadFile($file)
+ {
+ return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH);
+ }
+
+ /**
+ * Parses the config elements (default, requirement, option).
+ *
+ * @param \DOMElement $node Element to parse that contains the configs
+ * @param string $path Full path of the XML file being processed
+ *
+ * @return array An array with the defaults as first item, requirements as second and options as third
+ *
+ * @throws \InvalidArgumentException When the XML is invalid
+ */
+ private function parseConfigs(\DOMElement $node, $path)
+ {
+ $defaults = array();
+ $requirements = array();
+ $options = array();
+ $condition = null;
+
+ foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
+ if ($node !== $n->parentNode) {
+ continue;
+ }
+
+ switch ($n->localName) {
+ case 'default':
+ if ($this->isElementValueNull($n)) {
+ $defaults[$n->getAttribute('key')] = null;
+ } else {
+ $defaults[$n->getAttribute('key')] = $this->parseDefaultsConfig($n, $path);
+ }
+
+ break;
+ case 'requirement':
+ $requirements[$n->getAttribute('key')] = trim($n->textContent);
+ break;
+ case 'option':
+ $options[$n->getAttribute('key')] = trim($n->textContent);
+ break;
+ case 'condition':
+ $condition = trim($n->textContent);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".', $n->localName, $path));
+ }
+ }
+
+ if ($controller = $node->getAttribute('controller')) {
+ if (isset($defaults['_controller'])) {
+ $name = $node->hasAttribute('id') ? sprintf('"%s"', $node->getAttribute('id')) : sprintf('the "%s" tag', $node->tagName);
+
+ throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for %s.', $path, $name));
+ }
+
+ $defaults['_controller'] = $controller;
+ }
+
+ return array($defaults, $requirements, $options, $condition);
+ }
+
+ /**
+ * Parses the "default" elements.
+ *
+ * @param \DOMElement $element The "default" element to parse
+ * @param string $path Full path of the XML file being processed
+ *
+ * @return array|bool|float|int|string|null The parsed value of the "default" element
+ */
+ private function parseDefaultsConfig(\DOMElement $element, $path)
+ {
+ if ($this->isElementValueNull($element)) {
+ return;
+ }
+
+ // Check for existing element nodes in the default element. There can
+ // only be a single element inside a default element. So this element
+ // (if one was found) can safely be returned.
+ foreach ($element->childNodes as $child) {
+ if (!$child instanceof \DOMElement) {
+ continue;
+ }
+
+ if (self::NAMESPACE_URI !== $child->namespaceURI) {
+ continue;
+ }
+
+ return $this->parseDefaultNode($child, $path);
+ }
+
+ // If the default element doesn't contain a nested "bool", "int", "float",
+ // "string", "list", or "map" element, the element contents will be treated
+ // as the string value of the associated default option.
+ return trim($element->textContent);
+ }
+
+ /**
+ * Recursively parses the value of a "default" element.
+ *
+ * @param \DOMElement $node The node value
+ * @param string $path Full path of the XML file being processed
+ *
+ * @return array|bool|float|int|string The parsed value
+ *
+ * @throws \InvalidArgumentException when the XML is invalid
+ */
+ private function parseDefaultNode(\DOMElement $node, $path)
+ {
+ if ($this->isElementValueNull($node)) {
+ return;
+ }
+
+ switch ($node->localName) {
+ case 'bool':
+ return 'true' === trim($node->nodeValue) || '1' === trim($node->nodeValue);
+ case 'int':
+ return (int) trim($node->nodeValue);
+ case 'float':
+ return (float) trim($node->nodeValue);
+ case 'string':
+ return trim($node->nodeValue);
+ case 'list':
+ $list = array();
+
+ foreach ($node->childNodes as $element) {
+ if (!$element instanceof \DOMElement) {
+ continue;
+ }
+
+ if (self::NAMESPACE_URI !== $element->namespaceURI) {
+ continue;
+ }
+
+ $list[] = $this->parseDefaultNode($element, $path);
+ }
+
+ return $list;
+ case 'map':
+ $map = array();
+
+ foreach ($node->childNodes as $element) {
+ if (!$element instanceof \DOMElement) {
+ continue;
+ }
+
+ if (self::NAMESPACE_URI !== $element->namespaceURI) {
+ continue;
+ }
+
+ $map[$element->getAttribute('key')] = $this->parseDefaultNode($element, $path);
+ }
+
+ return $map;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "bool", "int", "float", "string", "list", or "map".', $node->localName, $path));
+ }
+ }
+
+ private function isElementValueNull(\DOMElement $element)
+ {
+ $namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance';
+
+ if (!$element->hasAttributeNS($namespaceUri, 'nil')) {
+ return false;
+ }
+
+ return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil');
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/YamlFileLoader.php b/assets/php/vendor/symfony/routing/Loader/YamlFileLoader.php
new file mode 100644
index 0000000..f59f909
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/YamlFileLoader.php
@@ -0,0 +1,233 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Loader;
+
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Parser as YamlParser;
+use Symfony\Component\Config\Loader\FileLoader;
+
+/**
+ * YamlFileLoader loads Yaml routing files.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+class YamlFileLoader extends FileLoader
+{
+ private static $availableKeys = array(
+ 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller',
+ );
+ private $yamlParser;
+
+ /**
+ * Loads a Yaml file.
+ *
+ * @param string $file A Yaml file path
+ * @param string|null $type The resource type
+ *
+ * @return RouteCollection A RouteCollection instance
+ *
+ * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid
+ */
+ public function load($file, $type = null)
+ {
+ $path = $this->locator->locate($file);
+
+ if (!stream_is_local($path)) {
+ throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path));
+ }
+
+ if (!file_exists($path)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path));
+ }
+
+ if (null === $this->yamlParser) {
+ $this->yamlParser = new YamlParser();
+ }
+
+ $prevErrorHandler = set_error_handler(function ($level, $message, $script, $line) use ($file, &$prevErrorHandler) {
+ $message = E_USER_DEPRECATED === $level ? preg_replace('/ on line \d+/', ' in "'.$file.'"$0', $message) : $message;
+
+ return $prevErrorHandler ? $prevErrorHandler($level, $message, $script, $line) : false;
+ });
+
+ try {
+ $parsedConfig = $this->yamlParser->parseFile($path);
+ } catch (ParseException $e) {
+ throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
+ } finally {
+ restore_error_handler();
+ }
+
+ $collection = new RouteCollection();
+ $collection->addResource(new FileResource($path));
+
+ // empty file
+ if (null === $parsedConfig) {
+ return $collection;
+ }
+
+ // not an array
+ if (!is_array($parsedConfig)) {
+ throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path));
+ }
+
+ foreach ($parsedConfig as $name => $config) {
+ $this->validate($config, $name, $path);
+
+ if (isset($config['resource'])) {
+ $this->parseImport($collection, $config, $path, $file);
+ } else {
+ $this->parseRoute($collection, $name, $config, $path);
+ }
+ }
+
+ return $collection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, $type = null)
+ {
+ return is_string($resource) && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true) && (!$type || 'yaml' === $type);
+ }
+
+ /**
+ * Parses a route and adds it to the RouteCollection.
+ *
+ * @param RouteCollection $collection A RouteCollection instance
+ * @param string $name Route name
+ * @param array $config Route definition
+ * @param string $path Full path of the YAML file being processed
+ */
+ protected function parseRoute(RouteCollection $collection, $name, array $config, $path)
+ {
+ $defaults = isset($config['defaults']) ? $config['defaults'] : array();
+ $requirements = isset($config['requirements']) ? $config['requirements'] : array();
+ $options = isset($config['options']) ? $config['options'] : array();
+ $host = isset($config['host']) ? $config['host'] : '';
+ $schemes = isset($config['schemes']) ? $config['schemes'] : array();
+ $methods = isset($config['methods']) ? $config['methods'] : array();
+ $condition = isset($config['condition']) ? $config['condition'] : null;
+
+ if (isset($config['controller'])) {
+ $defaults['_controller'] = $config['controller'];
+ }
+
+ $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
+
+ $collection->add($name, $route);
+ }
+
+ /**
+ * Parses an import and adds the routes in the resource to the RouteCollection.
+ *
+ * @param RouteCollection $collection A RouteCollection instance
+ * @param array $config Route definition
+ * @param string $path Full path of the YAML file being processed
+ * @param string $file Loaded file name
+ */
+ protected function parseImport(RouteCollection $collection, array $config, $path, $file)
+ {
+ $type = isset($config['type']) ? $config['type'] : null;
+ $prefix = isset($config['prefix']) ? $config['prefix'] : '';
+ $defaults = isset($config['defaults']) ? $config['defaults'] : array();
+ $requirements = isset($config['requirements']) ? $config['requirements'] : array();
+ $options = isset($config['options']) ? $config['options'] : array();
+ $host = isset($config['host']) ? $config['host'] : null;
+ $condition = isset($config['condition']) ? $config['condition'] : null;
+ $schemes = isset($config['schemes']) ? $config['schemes'] : null;
+ $methods = isset($config['methods']) ? $config['methods'] : null;
+
+ if (isset($config['controller'])) {
+ $defaults['_controller'] = $config['controller'];
+ }
+
+ $this->setCurrentDir(dirname($path));
+
+ $imported = $this->import($config['resource'], $type, false, $file);
+
+ if (!is_array($imported)) {
+ $imported = array($imported);
+ }
+
+ foreach ($imported as $subCollection) {
+ /* @var $subCollection RouteCollection */
+ $subCollection->addPrefix($prefix);
+ if (null !== $host) {
+ $subCollection->setHost($host);
+ }
+ if (null !== $condition) {
+ $subCollection->setCondition($condition);
+ }
+ if (null !== $schemes) {
+ $subCollection->setSchemes($schemes);
+ }
+ if (null !== $methods) {
+ $subCollection->setMethods($methods);
+ }
+ $subCollection->addDefaults($defaults);
+ $subCollection->addRequirements($requirements);
+ $subCollection->addOptions($options);
+
+ $collection->addCollection($subCollection);
+ }
+ }
+
+ /**
+ * Validates the route configuration.
+ *
+ * @param array $config A resource config
+ * @param string $name The config key
+ * @param string $path The loaded file path
+ *
+ * @throws \InvalidArgumentException If one of the provided config keys is not supported,
+ * something is missing or the combination is nonsense
+ */
+ protected function validate($config, $name, $path)
+ {
+ if (!is_array($config)) {
+ throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path));
+ }
+ if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".',
+ $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys)
+ ));
+ }
+ if (isset($config['resource']) && isset($config['path'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.',
+ $path, $name
+ ));
+ }
+ if (!isset($config['resource']) && isset($config['type'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.',
+ $name, $path
+ ));
+ }
+ if (!isset($config['resource']) && !isset($config['path'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'You must define a "path" for the route "%s" in file "%s".',
+ $name, $path
+ ));
+ }
+ if (isset($config['controller']) && isset($config['defaults']['_controller'])) {
+ throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name));
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd b/assets/php/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd
new file mode 100644
index 0000000..a97111a
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php
new file mode 100644
index 0000000..6916297
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php
@@ -0,0 +1,159 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher\Dumper;
+
+/**
+ * Collection of routes.
+ *
+ * @author Arnaud Le Blanc
+ *
+ * @internal
+ */
+class DumperCollection implements \IteratorAggregate
+{
+ /**
+ * @var DumperCollection|null
+ */
+ private $parent;
+
+ /**
+ * @var DumperCollection[]|DumperRoute[]
+ */
+ private $children = array();
+
+ /**
+ * @var array
+ */
+ private $attributes = array();
+
+ /**
+ * Returns the children routes and collections.
+ *
+ * @return self[]|DumperRoute[]
+ */
+ public function all()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Adds a route or collection.
+ *
+ * @param DumperRoute|DumperCollection The route or collection
+ */
+ public function add($child)
+ {
+ if ($child instanceof self) {
+ $child->setParent($this);
+ }
+ $this->children[] = $child;
+ }
+
+ /**
+ * Sets children.
+ *
+ * @param array $children The children
+ */
+ public function setAll(array $children)
+ {
+ foreach ($children as $child) {
+ if ($child instanceof self) {
+ $child->setParent($this);
+ }
+ }
+ $this->children = $children;
+ }
+
+ /**
+ * Returns an iterator over the children.
+ *
+ * @return \Iterator|DumperCollection[]|DumperRoute[] The iterator
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->children);
+ }
+
+ /**
+ * Returns the root of the collection.
+ *
+ * @return self The root collection
+ */
+ public function getRoot()
+ {
+ return (null !== $this->parent) ? $this->parent->getRoot() : $this;
+ }
+
+ /**
+ * Returns the parent collection.
+ *
+ * @return self|null The parent collection or null if the collection has no parent
+ */
+ protected function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Sets the parent collection.
+ */
+ protected function setParent(DumperCollection $parent)
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Returns true if the attribute is defined.
+ *
+ * @param string $name The attribute name
+ *
+ * @return bool true if the attribute is defined, false otherwise
+ */
+ public function hasAttribute($name)
+ {
+ return array_key_exists($name, $this->attributes);
+ }
+
+ /**
+ * Returns an attribute by name.
+ *
+ * @param string $name The attribute name
+ * @param mixed $default Default value is the attribute doesn't exist
+ *
+ * @return mixed The attribute value
+ */
+ public function getAttribute($name, $default = null)
+ {
+ return $this->hasAttribute($name) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * Sets an attribute by name.
+ *
+ * @param string $name The attribute name
+ * @param mixed $value The attribute value
+ */
+ public function setAttribute($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ /**
+ * Sets multiple attributes.
+ *
+ * @param array $attributes The attributes
+ */
+ public function setAttributes($attributes)
+ {
+ $this->attributes = $attributes;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php
new file mode 100644
index 0000000..c71989a
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher\Dumper;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Container for a Route.
+ *
+ * @author Arnaud Le Blanc
+ *
+ * @internal
+ */
+class DumperRoute
+{
+ private $name;
+ private $route;
+
+ /**
+ * @param string $name The route name
+ * @param Route $route The route
+ */
+ public function __construct($name, Route $route)
+ {
+ $this->name = $name;
+ $this->route = $route;
+ }
+
+ /**
+ * Returns the route name.
+ *
+ * @return string The route name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns the route.
+ *
+ * @return Route The route
+ */
+ public function getRoute()
+ {
+ return $this->route;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php
new file mode 100644
index 0000000..ea51ab4
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher\Dumper;
+
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * MatcherDumper is the abstract class for all built-in matcher dumpers.
+ *
+ * @author Fabien Potencier
+ */
+abstract class MatcherDumper implements MatcherDumperInterface
+{
+ private $routes;
+
+ public function __construct(RouteCollection $routes)
+ {
+ $this->routes = $routes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRoutes()
+ {
+ return $this->routes;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php
new file mode 100644
index 0000000..5e7c134
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher\Dumper;
+
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * MatcherDumperInterface is the interface that all matcher dumper classes must implement.
+ *
+ * @author Fabien Potencier
+ */
+interface MatcherDumperInterface
+{
+ /**
+ * Dumps a set of routes to a string representation of executable code
+ * that can then be used to match a request against these routes.
+ *
+ * @param array $options An array of options
+ *
+ * @return string Executable code
+ */
+ public function dump(array $options = array());
+
+ /**
+ * Gets the routes to dump.
+ *
+ * @return RouteCollection A RouteCollection instance
+ */
+ public function getRoutes();
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php
new file mode 100644
index 0000000..40d8df6
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php
@@ -0,0 +1,429 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher\Dumper;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
+
+/**
+ * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ * @author Arnaud Le Blanc
+ */
+class PhpMatcherDumper extends MatcherDumper
+{
+ private $expressionLanguage;
+
+ /**
+ * @var ExpressionFunctionProviderInterface[]
+ */
+ private $expressionLanguageProviders = array();
+
+ /**
+ * Dumps a set of routes to a PHP class.
+ *
+ * Available options:
+ *
+ * * class: The class name
+ * * base_class: The base class name
+ *
+ * @param array $options An array of options
+ *
+ * @return string A PHP class representing the matcher class
+ */
+ public function dump(array $options = array())
+ {
+ $options = array_replace(array(
+ 'class' => 'ProjectUrlMatcher',
+ 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
+ ), $options);
+
+ // trailing slash support is only enabled if we know how to redirect the user
+ $interfaces = class_implements($options['base_class']);
+ $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']);
+
+ return <<context = \$context;
+ }
+
+{$this->generateMatchMethod($supportsRedirections)}
+}
+
+EOF;
+ }
+
+ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
+ {
+ $this->expressionLanguageProviders[] = $provider;
+ }
+
+ /**
+ * Generates the code for the match method implementing UrlMatcherInterface.
+ *
+ * @param bool $supportsRedirections Whether redirections are supported by the base class
+ *
+ * @return string Match method as PHP code
+ */
+ private function generateMatchMethod($supportsRedirections)
+ {
+ $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n");
+
+ return <<context;
+ \$request = \$this->request ?: \$this->createRequest(\$pathinfo);
+ \$requestMethod = \$canonicalMethod = \$context->getMethod();
+
+ if ('HEAD' === \$requestMethod) {
+ \$canonicalMethod = 'GET';
+ }
+
+$code
+
+ throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException();
+ }
+EOF;
+ }
+
+ /**
+ * Generates PHP code to match a RouteCollection with all its routes.
+ *
+ * @param RouteCollection $routes A RouteCollection instance
+ * @param bool $supportsRedirections Whether redirections are supported by the base class
+ *
+ * @return string PHP code
+ */
+ private function compileRoutes(RouteCollection $routes, $supportsRedirections)
+ {
+ $fetchedHost = false;
+ $groups = $this->groupRoutesByHostRegex($routes);
+ $code = '';
+
+ foreach ($groups as $collection) {
+ if (null !== $regex = $collection->getAttribute('host_regex')) {
+ if (!$fetchedHost) {
+ $code .= " \$host = \$context->getHost();\n\n";
+ $fetchedHost = true;
+ }
+
+ $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true));
+ }
+
+ $tree = $this->buildStaticPrefixCollection($collection);
+ $groupCode = $this->compileStaticPrefixRoutes($tree, $supportsRedirections);
+
+ if (null !== $regex) {
+ // apply extra indention at each line (except empty ones)
+ $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode);
+ $code .= $groupCode;
+ $code .= " }\n\n";
+ } else {
+ $code .= $groupCode;
+ }
+ }
+
+ // used to display the Welcome Page in apps that don't define a homepage
+ $code .= " if ('/' === \$pathinfo && !\$allow) {\n";
+ $code .= " throw new Symfony\Component\Routing\Exception\NoConfigurationException();\n";
+ $code .= " }\n";
+
+ return $code;
+ }
+
+ private function buildStaticPrefixCollection(DumperCollection $collection)
+ {
+ $prefixCollection = new StaticPrefixCollection();
+
+ foreach ($collection as $dumperRoute) {
+ $prefix = $dumperRoute->getRoute()->compile()->getStaticPrefix();
+ $prefixCollection->addRoute($prefix, $dumperRoute);
+ }
+
+ $prefixCollection->optimizeGroups();
+
+ return $prefixCollection;
+ }
+
+ /**
+ * Generates PHP code to match a tree of routes.
+ *
+ * @param StaticPrefixCollection $collection A StaticPrefixCollection instance
+ * @param bool $supportsRedirections Whether redirections are supported by the base class
+ * @param string $ifOrElseIf either "if" or "elseif" to influence chaining
+ *
+ * @return string PHP code
+ */
+ private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $supportsRedirections, $ifOrElseIf = 'if')
+ {
+ $code = '';
+ $prefix = $collection->getPrefix();
+
+ if (!empty($prefix) && '/' !== $prefix) {
+ $code .= sprintf(" %s (0 === strpos(\$pathinfo, %s)) {\n", $ifOrElseIf, var_export($prefix, true));
+ }
+
+ $ifOrElseIf = 'if';
+
+ foreach ($collection->getItems() as $route) {
+ if ($route instanceof StaticPrefixCollection) {
+ $code .= $this->compileStaticPrefixRoutes($route, $supportsRedirections, $ifOrElseIf);
+ $ifOrElseIf = 'elseif';
+ } else {
+ $code .= $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $supportsRedirections, $prefix)."\n";
+ $ifOrElseIf = 'if';
+ }
+ }
+
+ if (!empty($prefix) && '/' !== $prefix) {
+ $code .= " }\n\n";
+ // apply extra indention at each line (except empty ones)
+ $code = preg_replace('/^.{2,}$/m', ' $0', $code);
+ }
+
+ return $code;
+ }
+
+ /**
+ * Compiles a single Route to PHP code used to match it against the path info.
+ *
+ * @param Route $route A Route instance
+ * @param string $name The name of the Route
+ * @param bool $supportsRedirections Whether redirections are supported by the base class
+ * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code
+ *
+ * @return string PHP code
+ *
+ * @throws \LogicException
+ */
+ private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null)
+ {
+ $code = '';
+ $compiledRoute = $route->compile();
+ $conditions = array();
+ $hasTrailingSlash = false;
+ $matches = false;
+ $hostMatches = false;
+ $methods = $route->getMethods();
+
+ $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('GET', $methods));
+ $regex = $compiledRoute->getRegex();
+
+ if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#'.('u' === substr($regex, -1) ? 'u' : ''), $regex, $m)) {
+ if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) {
+ $conditions[] = sprintf('%s === $trimmedPathinfo', var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
+ $hasTrailingSlash = true;
+ } else {
+ $conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true));
+ }
+ } else {
+ if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) {
+ $conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true));
+ }
+
+ if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) {
+ $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
+ $hasTrailingSlash = true;
+ }
+ $conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true));
+
+ $matches = true;
+ }
+
+ if ($compiledRoute->getHostVariables()) {
+ $hostMatches = true;
+ }
+
+ if ($route->getCondition()) {
+ $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
+ }
+
+ $conditions = implode(' && ', $conditions);
+
+ $code .= << '$name')";
+
+ $code .= sprintf(
+ " \$ret = \$this->mergeDefaults(array_replace(%s), %s);\n",
+ implode(', ', $vars),
+ str_replace("\n", '', var_export($route->getDefaults(), true))
+ );
+ } elseif ($route->getDefaults()) {
+ $code .= sprintf(" \$ret = %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true)));
+ } else {
+ $code .= sprintf(" \$ret = array('_route' => '%s');\n", $name);
+ }
+
+ if ($hasTrailingSlash) {
+ $code .= <<redirect(\$rawPathinfo.'/', '$name'));
+ }
+
+
+EOF;
+ }
+
+ if ($methods) {
+ $methodVariable = in_array('GET', $methods) ? '$canonicalMethod' : '$requestMethod';
+ $methods = implode("', '", $methods);
+ }
+
+ if ($schemes = $route->getSchemes()) {
+ if (!$supportsRedirections) {
+ throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
+ }
+ $schemes = str_replace("\n", '', var_export(array_flip($schemes), true));
+ if ($methods) {
+ $code .= <<getScheme()]);
+ if (!in_array($methodVariable, array('$methods'))) {
+ if (\$hasRequiredScheme) {
+ \$allow = array_merge(\$allow, array('$methods'));
+ }
+ goto $gotoname;
+ }
+ if (!\$hasRequiredScheme) {
+ if ('GET' !== \$canonicalMethod) {
+ goto $gotoname;
+ }
+
+ return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)));
+ }
+
+
+EOF;
+ } else {
+ $code .= <<getScheme()])) {
+ if ('GET' !== \$canonicalMethod) {
+ goto $gotoname;
+ }
+
+ return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)));
+ }
+
+
+EOF;
+ }
+ } elseif ($methods) {
+ $code .= <<setAttribute('host_regex', null);
+ $groups->add($currentGroup);
+
+ foreach ($routes as $name => $route) {
+ $hostRegex = $route->compile()->getHostRegex();
+ if ($currentGroup->getAttribute('host_regex') !== $hostRegex) {
+ $currentGroup = new DumperCollection();
+ $currentGroup->setAttribute('host_regex', $hostRegex);
+ $groups->add($currentGroup);
+ }
+ $currentGroup->add(new DumperRoute($name, $route));
+ }
+
+ return $groups;
+ }
+
+ private function getExpressionLanguage()
+ {
+ if (null === $this->expressionLanguage) {
+ if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
+ throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
+ }
+ $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
+ }
+
+ return $this->expressionLanguage;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php b/assets/php/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php
new file mode 100644
index 0000000..7365808
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php
@@ -0,0 +1,238 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher\Dumper;
+
+/**
+ * Prefix tree of routes preserving routes order.
+ *
+ * @author Frank de Jonge
+ *
+ * @internal
+ */
+class StaticPrefixCollection
+{
+ /**
+ * @var string
+ */
+ private $prefix;
+
+ /**
+ * @var array[]|StaticPrefixCollection[]
+ */
+ private $items = array();
+
+ /**
+ * @var int
+ */
+ private $matchStart = 0;
+
+ public function __construct($prefix = '')
+ {
+ $this->prefix = $prefix;
+ }
+
+ public function getPrefix()
+ {
+ return $this->prefix;
+ }
+
+ /**
+ * @return mixed[]|StaticPrefixCollection[]
+ */
+ public function getItems()
+ {
+ return $this->items;
+ }
+
+ /**
+ * Adds a route to a group.
+ *
+ * @param string $prefix
+ * @param mixed $route
+ */
+ public function addRoute($prefix, $route)
+ {
+ $prefix = '/' === $prefix ? $prefix : rtrim($prefix, '/');
+ $this->guardAgainstAddingNotAcceptedRoutes($prefix);
+
+ if ($this->prefix === $prefix) {
+ // When a prefix is exactly the same as the base we move up the match start position.
+ // This is needed because otherwise routes that come afterwards have higher precedence
+ // than a possible regular expression, which goes against the input order sorting.
+ $this->items[] = array($prefix, $route);
+ $this->matchStart = count($this->items);
+
+ return;
+ }
+
+ foreach ($this->items as $i => $item) {
+ if ($i < $this->matchStart) {
+ continue;
+ }
+
+ if ($item instanceof self && $item->accepts($prefix)) {
+ $item->addRoute($prefix, $route);
+
+ return;
+ }
+
+ $group = $this->groupWithItem($item, $prefix, $route);
+
+ if ($group instanceof self) {
+ $this->items[$i] = $group;
+
+ return;
+ }
+ }
+
+ // No optimised case was found, in this case we simple add the route for possible
+ // grouping when new routes are added.
+ $this->items[] = array($prefix, $route);
+ }
+
+ /**
+ * Tries to combine a route with another route or group.
+ *
+ * @param StaticPrefixCollection|array $item
+ * @param string $prefix
+ * @param mixed $route
+ *
+ * @return null|StaticPrefixCollection
+ */
+ private function groupWithItem($item, $prefix, $route)
+ {
+ $itemPrefix = $item instanceof self ? $item->prefix : $item[0];
+ $commonPrefix = $this->detectCommonPrefix($prefix, $itemPrefix);
+
+ if (!$commonPrefix) {
+ return;
+ }
+
+ $child = new self($commonPrefix);
+
+ if ($item instanceof self) {
+ $child->items = array($item);
+ } else {
+ $child->addRoute($item[0], $item[1]);
+ }
+
+ $child->addRoute($prefix, $route);
+
+ return $child;
+ }
+
+ /**
+ * Checks whether a prefix can be contained within the group.
+ *
+ * @param string $prefix
+ *
+ * @return bool Whether a prefix could belong in a given group
+ */
+ private function accepts($prefix)
+ {
+ return '' === $this->prefix || 0 === strpos($prefix, $this->prefix);
+ }
+
+ /**
+ * Detects whether there's a common prefix relative to the group prefix and returns it.
+ *
+ * @param string $prefix
+ * @param string $anotherPrefix
+ *
+ * @return false|string A common prefix, longer than the base/group prefix, or false when none available
+ */
+ private function detectCommonPrefix($prefix, $anotherPrefix)
+ {
+ $baseLength = strlen($this->prefix);
+ $commonLength = $baseLength;
+ $end = min(strlen($prefix), strlen($anotherPrefix));
+
+ for ($i = $baseLength; $i <= $end; ++$i) {
+ if (substr($prefix, 0, $i) !== substr($anotherPrefix, 0, $i)) {
+ break;
+ }
+
+ $commonLength = $i;
+ }
+
+ $commonPrefix = rtrim(substr($prefix, 0, $commonLength), '/');
+
+ if (strlen($commonPrefix) > $baseLength) {
+ return $commonPrefix;
+ }
+
+ return false;
+ }
+
+ /**
+ * Optimizes the tree by inlining items from groups with less than 3 items.
+ */
+ public function optimizeGroups()
+ {
+ $index = -1;
+
+ while (isset($this->items[++$index])) {
+ $item = $this->items[$index];
+
+ if ($item instanceof self) {
+ $item->optimizeGroups();
+
+ // When a group contains only two items there's no reason to optimize because at minimum
+ // the amount of prefix check is 2. In this case inline the group.
+ if ($item->shouldBeInlined()) {
+ array_splice($this->items, $index, 1, $item->items);
+
+ // Lower index to pass through the same index again after optimizing.
+ // The first item of the replacements might be a group needing optimization.
+ --$index;
+ }
+ }
+ }
+ }
+
+ private function shouldBeInlined()
+ {
+ if (count($this->items) >= 3) {
+ return false;
+ }
+
+ foreach ($this->items as $item) {
+ if ($item instanceof self) {
+ return true;
+ }
+ }
+
+ foreach ($this->items as $item) {
+ if (is_array($item) && $item[0] === $this->prefix) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Guards against adding incompatible prefixes in a group.
+ *
+ * @param string $prefix
+ *
+ * @throws \LogicException when a prefix does not belong in a group
+ */
+ private function guardAgainstAddingNotAcceptedRoutes($prefix)
+ {
+ if (!$this->accepts($prefix)) {
+ $message = sprintf('Could not add route with prefix %s to collection with prefix %s', $prefix, $this->prefix);
+
+ throw new \LogicException($message);
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php b/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php
new file mode 100644
index 0000000..3770a9c
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher;
+
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Route;
+
+/**
+ * @author Fabien Potencier
+ */
+abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function match($pathinfo)
+ {
+ try {
+ $parameters = parent::match($pathinfo);
+ } catch (ResourceNotFoundException $e) {
+ if ('/' === substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
+ throw $e;
+ }
+
+ try {
+ $parameters = parent::match($pathinfo.'/');
+
+ return array_replace($parameters, $this->redirect($pathinfo.'/', isset($parameters['_route']) ? $parameters['_route'] : null));
+ } catch (ResourceNotFoundException $e2) {
+ throw $e;
+ }
+ }
+
+ return $parameters;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function handleRouteRequirements($pathinfo, $name, Route $route)
+ {
+ // expression condition
+ if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) {
+ return array(self::REQUIREMENT_MISMATCH, null);
+ }
+
+ // check HTTP scheme requirement
+ $scheme = $this->context->getScheme();
+ $schemes = $route->getSchemes();
+ if ($schemes && !$route->hasScheme($scheme)) {
+ return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes)));
+ }
+
+ return array(self::REQUIREMENT_MATCH, null);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php b/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php
new file mode 100644
index 0000000..7c27bc8
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher;
+
+/**
+ * RedirectableUrlMatcherInterface knows how to redirect the user.
+ *
+ * @author Fabien Potencier
+ */
+interface RedirectableUrlMatcherInterface
+{
+ /**
+ * Redirects the user to another URL.
+ *
+ * @param string $path The path info to redirect to
+ * @param string $route The route name that matched
+ * @param string|null $scheme The URL scheme (null to keep the current one)
+ *
+ * @return array An array of parameters
+ */
+ public function redirect($path, $route, $scheme = null);
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/RequestMatcherInterface.php b/assets/php/vendor/symfony/routing/Matcher/RequestMatcherInterface.php
new file mode 100644
index 0000000..1eef778
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/RequestMatcherInterface.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Exception\NoConfigurationException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+
+/**
+ * RequestMatcherInterface is the interface that all request matcher classes must implement.
+ *
+ * @author Fabien Potencier
+ */
+interface RequestMatcherInterface
+{
+ /**
+ * Tries to match a request with a set of routes.
+ *
+ * If the matcher can not find information, it must throw one of the exceptions documented
+ * below.
+ *
+ * @return array An array of parameters
+ *
+ * @throws NoConfigurationException If no routing configuration could be found
+ * @throws ResourceNotFoundException If no matching resource could be found
+ * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed
+ */
+ public function matchRequest(Request $request);
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php b/assets/php/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php
new file mode 100644
index 0000000..9085be0
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Exception\ExceptionInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * TraceableUrlMatcher helps debug path info matching by tracing the match.
+ *
+ * @author Fabien Potencier
+ */
+class TraceableUrlMatcher extends UrlMatcher
+{
+ const ROUTE_DOES_NOT_MATCH = 0;
+ const ROUTE_ALMOST_MATCHES = 1;
+ const ROUTE_MATCHES = 2;
+
+ protected $traces;
+
+ public function getTraces($pathinfo)
+ {
+ $this->traces = array();
+
+ try {
+ $this->match($pathinfo);
+ } catch (ExceptionInterface $e) {
+ }
+
+ return $this->traces;
+ }
+
+ public function getTracesForRequest(Request $request)
+ {
+ $this->request = $request;
+ $traces = $this->getTraces($request->getPathInfo());
+ $this->request = null;
+
+ return $traces;
+ }
+
+ protected function matchCollection($pathinfo, RouteCollection $routes)
+ {
+ foreach ($routes as $name => $route) {
+ $compiledRoute = $route->compile();
+
+ if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
+ // does it match without any requirements?
+ $r = new Route($route->getPath(), $route->getDefaults(), array(), $route->getOptions());
+ $cr = $r->compile();
+ if (!preg_match($cr->getRegex(), $pathinfo)) {
+ $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route);
+
+ continue;
+ }
+
+ foreach ($route->getRequirements() as $n => $regex) {
+ $r = new Route($route->getPath(), $route->getDefaults(), array($n => $regex), $route->getOptions());
+ $cr = $r->compile();
+
+ if (in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) {
+ $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route);
+
+ continue 2;
+ }
+ }
+
+ continue;
+ }
+
+ // check host requirement
+ $hostMatches = array();
+ if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
+ $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route);
+
+ continue;
+ }
+
+ // check HTTP method requirement
+ if ($requiredMethods = $route->getMethods()) {
+ // HEAD and GET are equivalent as per RFC
+ if ('HEAD' === $method = $this->context->getMethod()) {
+ $method = 'GET';
+ }
+
+ if (!in_array($method, $requiredMethods)) {
+ $this->allow = array_merge($this->allow, $requiredMethods);
+
+ $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route);
+
+ continue;
+ }
+ }
+
+ // check condition
+ if ($condition = $route->getCondition()) {
+ if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) {
+ $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route);
+
+ continue;
+ }
+ }
+
+ // check HTTP scheme requirement
+ if ($requiredSchemes = $route->getSchemes()) {
+ $scheme = $this->context->getScheme();
+
+ if (!$route->hasScheme($scheme)) {
+ $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s); the user will be redirected to first required scheme', $scheme, implode(', ', $requiredSchemes)), self::ROUTE_ALMOST_MATCHES, $name, $route);
+
+ return true;
+ }
+ }
+
+ $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route);
+
+ return true;
+ }
+ }
+
+ private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null)
+ {
+ $this->traces[] = array(
+ 'log' => $log,
+ 'name' => $name,
+ 'level' => $level,
+ 'path' => null !== $route ? $route->getPath() : null,
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/UrlMatcher.php b/assets/php/vendor/symfony/routing/Matcher/UrlMatcher.php
new file mode 100644
index 0000000..445cfc4
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/UrlMatcher.php
@@ -0,0 +1,252 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher;
+
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Exception\NoConfigurationException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
+
+/**
+ * UrlMatcher matches URL based on a set of routes.
+ *
+ * @author Fabien Potencier
+ */
+class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
+{
+ const REQUIREMENT_MATCH = 0;
+ const REQUIREMENT_MISMATCH = 1;
+ const ROUTE_MATCH = 2;
+
+ protected $context;
+ protected $allow = array();
+ protected $routes;
+ protected $request;
+ protected $expressionLanguage;
+
+ /**
+ * @var ExpressionFunctionProviderInterface[]
+ */
+ protected $expressionLanguageProviders = array();
+
+ public function __construct(RouteCollection $routes, RequestContext $context)
+ {
+ $this->routes = $routes;
+ $this->context = $context;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setContext(RequestContext $context)
+ {
+ $this->context = $context;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function match($pathinfo)
+ {
+ $this->allow = array();
+
+ if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
+ return $ret;
+ }
+
+ if ('/' === $pathinfo && !$this->allow) {
+ throw new NoConfigurationException();
+ }
+
+ throw 0 < count($this->allow)
+ ? new MethodNotAllowedException(array_unique($this->allow))
+ : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function matchRequest(Request $request)
+ {
+ $this->request = $request;
+
+ $ret = $this->match($request->getPathInfo());
+
+ $this->request = null;
+
+ return $ret;
+ }
+
+ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
+ {
+ $this->expressionLanguageProviders[] = $provider;
+ }
+
+ /**
+ * Tries to match a URL with a set of routes.
+ *
+ * @param string $pathinfo The path info to be parsed
+ * @param RouteCollection $routes The set of routes
+ *
+ * @return array An array of parameters
+ *
+ * @throws NoConfigurationException If no routing configuration could be found
+ * @throws ResourceNotFoundException If the resource could not be found
+ * @throws MethodNotAllowedException If the resource was found but the request method is not allowed
+ */
+ protected function matchCollection($pathinfo, RouteCollection $routes)
+ {
+ foreach ($routes as $name => $route) {
+ $compiledRoute = $route->compile();
+
+ // check the static prefix of the URL first. Only use the more expensive preg_match when it matches
+ if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) {
+ continue;
+ }
+
+ if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
+ continue;
+ }
+
+ $hostMatches = array();
+ if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
+ continue;
+ }
+
+ $status = $this->handleRouteRequirements($pathinfo, $name, $route);
+
+ if (self::REQUIREMENT_MISMATCH === $status[0]) {
+ continue;
+ }
+
+ // check HTTP method requirement
+ if ($requiredMethods = $route->getMethods()) {
+ // HEAD and GET are equivalent as per RFC
+ if ('HEAD' === $method = $this->context->getMethod()) {
+ $method = 'GET';
+ }
+
+ if (!in_array($method, $requiredMethods)) {
+ if (self::REQUIREMENT_MATCH === $status[0]) {
+ $this->allow = array_merge($this->allow, $requiredMethods);
+ }
+
+ continue;
+ }
+ }
+
+ return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array()));
+ }
+ }
+
+ /**
+ * Returns an array of values to use as request attributes.
+ *
+ * As this method requires the Route object, it is not available
+ * in matchers that do not have access to the matched Route instance
+ * (like the PHP and Apache matcher dumpers).
+ *
+ * @param Route $route The route we are matching against
+ * @param string $name The name of the route
+ * @param array $attributes An array of attributes from the matcher
+ *
+ * @return array An array of parameters
+ */
+ protected function getAttributes(Route $route, $name, array $attributes)
+ {
+ $attributes['_route'] = $name;
+
+ return $this->mergeDefaults($attributes, $route->getDefaults());
+ }
+
+ /**
+ * Handles specific route requirements.
+ *
+ * @param string $pathinfo The path
+ * @param string $name The route name
+ * @param Route $route The route
+ *
+ * @return array The first element represents the status, the second contains additional information
+ */
+ protected function handleRouteRequirements($pathinfo, $name, Route $route)
+ {
+ // expression condition
+ if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) {
+ return array(self::REQUIREMENT_MISMATCH, null);
+ }
+
+ // check HTTP scheme requirement
+ $scheme = $this->context->getScheme();
+ $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
+
+ return array($status, null);
+ }
+
+ /**
+ * Get merged default parameters.
+ *
+ * @param array $params The parameters
+ * @param array $defaults The defaults
+ *
+ * @return array Merged default parameters
+ */
+ protected function mergeDefaults($params, $defaults)
+ {
+ foreach ($params as $key => $value) {
+ if (!is_int($key)) {
+ $defaults[$key] = $value;
+ }
+ }
+
+ return $defaults;
+ }
+
+ protected function getExpressionLanguage()
+ {
+ if (null === $this->expressionLanguage) {
+ if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
+ throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
+ }
+ $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
+ }
+
+ return $this->expressionLanguage;
+ }
+
+ /**
+ * @internal
+ */
+ protected function createRequest($pathinfo)
+ {
+ if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
+ return null;
+ }
+
+ return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), array(), array(), array(
+ 'SCRIPT_FILENAME' => $this->context->getBaseUrl(),
+ 'SCRIPT_NAME' => $this->context->getBaseUrl(),
+ ));
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Matcher/UrlMatcherInterface.php b/assets/php/vendor/symfony/routing/Matcher/UrlMatcherInterface.php
new file mode 100644
index 0000000..2c7c313
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Matcher/UrlMatcherInterface.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher;
+
+use Symfony\Component\Routing\Exception\NoConfigurationException;
+use Symfony\Component\Routing\RequestContextAwareInterface;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+
+/**
+ * UrlMatcherInterface is the interface that all URL matcher classes must implement.
+ *
+ * @author Fabien Potencier
+ */
+interface UrlMatcherInterface extends RequestContextAwareInterface
+{
+ /**
+ * Tries to match a URL path with a set of routes.
+ *
+ * If the matcher can not find information, it must throw one of the exceptions documented
+ * below.
+ *
+ * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded)
+ *
+ * @return array An array of parameters
+ *
+ * @throws NoConfigurationException If no routing configuration could be found
+ * @throws ResourceNotFoundException If the resource could not be found
+ * @throws MethodNotAllowedException If the resource was found but the request method is not allowed
+ */
+ public function match($pathinfo);
+}
diff --git a/assets/php/vendor/symfony/routing/README.md b/assets/php/vendor/symfony/routing/README.md
new file mode 100644
index 0000000..88fb1fd
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/README.md
@@ -0,0 +1,13 @@
+Routing Component
+=================
+
+The Routing component maps an HTTP request to a set of configuration variables.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/routing/index.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/assets/php/vendor/symfony/routing/RequestContext.php b/assets/php/vendor/symfony/routing/RequestContext.php
new file mode 100644
index 0000000..d62a776
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/RequestContext.php
@@ -0,0 +1,336 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Holds information about the current request.
+ *
+ * This class implements a fluent interface.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+class RequestContext
+{
+ private $baseUrl;
+ private $pathInfo;
+ private $method;
+ private $host;
+ private $scheme;
+ private $httpPort;
+ private $httpsPort;
+ private $queryString;
+ private $parameters = array();
+
+ /**
+ * @param string $baseUrl The base URL
+ * @param string $method The HTTP method
+ * @param string $host The HTTP host name
+ * @param string $scheme The HTTP scheme
+ * @param int $httpPort The HTTP port
+ * @param int $httpsPort The HTTPS port
+ * @param string $path The path
+ * @param string $queryString The query string
+ */
+ public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/', $queryString = '')
+ {
+ $this->setBaseUrl($baseUrl);
+ $this->setMethod($method);
+ $this->setHost($host);
+ $this->setScheme($scheme);
+ $this->setHttpPort($httpPort);
+ $this->setHttpsPort($httpsPort);
+ $this->setPathInfo($path);
+ $this->setQueryString($queryString);
+ }
+
+ /**
+ * Updates the RequestContext information based on a HttpFoundation Request.
+ *
+ * @return $this
+ */
+ public function fromRequest(Request $request)
+ {
+ $this->setBaseUrl($request->getBaseUrl());
+ $this->setPathInfo($request->getPathInfo());
+ $this->setMethod($request->getMethod());
+ $this->setHost($request->getHost());
+ $this->setScheme($request->getScheme());
+ $this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort());
+ $this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort);
+ $this->setQueryString($request->server->get('QUERY_STRING', ''));
+
+ return $this;
+ }
+
+ /**
+ * Gets the base URL.
+ *
+ * @return string The base URL
+ */
+ public function getBaseUrl()
+ {
+ return $this->baseUrl;
+ }
+
+ /**
+ * Sets the base URL.
+ *
+ * @param string $baseUrl The base URL
+ *
+ * @return $this
+ */
+ public function setBaseUrl($baseUrl)
+ {
+ $this->baseUrl = $baseUrl;
+
+ return $this;
+ }
+
+ /**
+ * Gets the path info.
+ *
+ * @return string The path info
+ */
+ public function getPathInfo()
+ {
+ return $this->pathInfo;
+ }
+
+ /**
+ * Sets the path info.
+ *
+ * @param string $pathInfo The path info
+ *
+ * @return $this
+ */
+ public function setPathInfo($pathInfo)
+ {
+ $this->pathInfo = $pathInfo;
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTP method.
+ *
+ * The method is always an uppercased string.
+ *
+ * @return string The HTTP method
+ */
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ /**
+ * Sets the HTTP method.
+ *
+ * @param string $method The HTTP method
+ *
+ * @return $this
+ */
+ public function setMethod($method)
+ {
+ $this->method = strtoupper($method);
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTP host.
+ *
+ * The host is always lowercased because it must be treated case-insensitive.
+ *
+ * @return string The HTTP host
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Sets the HTTP host.
+ *
+ * @param string $host The HTTP host
+ *
+ * @return $this
+ */
+ public function setHost($host)
+ {
+ $this->host = strtolower($host);
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTP scheme.
+ *
+ * @return string The HTTP scheme
+ */
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ /**
+ * Sets the HTTP scheme.
+ *
+ * @param string $scheme The HTTP scheme
+ *
+ * @return $this
+ */
+ public function setScheme($scheme)
+ {
+ $this->scheme = strtolower($scheme);
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTP port.
+ *
+ * @return int The HTTP port
+ */
+ public function getHttpPort()
+ {
+ return $this->httpPort;
+ }
+
+ /**
+ * Sets the HTTP port.
+ *
+ * @param int $httpPort The HTTP port
+ *
+ * @return $this
+ */
+ public function setHttpPort($httpPort)
+ {
+ $this->httpPort = (int) $httpPort;
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTPS port.
+ *
+ * @return int The HTTPS port
+ */
+ public function getHttpsPort()
+ {
+ return $this->httpsPort;
+ }
+
+ /**
+ * Sets the HTTPS port.
+ *
+ * @param int $httpsPort The HTTPS port
+ *
+ * @return $this
+ */
+ public function setHttpsPort($httpsPort)
+ {
+ $this->httpsPort = (int) $httpsPort;
+
+ return $this;
+ }
+
+ /**
+ * Gets the query string.
+ *
+ * @return string The query string without the "?"
+ */
+ public function getQueryString()
+ {
+ return $this->queryString;
+ }
+
+ /**
+ * Sets the query string.
+ *
+ * @param string $queryString The query string (after "?")
+ *
+ * @return $this
+ */
+ public function setQueryString($queryString)
+ {
+ // string cast to be fault-tolerant, accepting null
+ $this->queryString = (string) $queryString;
+
+ return $this;
+ }
+
+ /**
+ * Returns the parameters.
+ *
+ * @return array The parameters
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Sets the parameters.
+ *
+ * @param array $parameters The parameters
+ *
+ * @return $this
+ */
+ public function setParameters(array $parameters)
+ {
+ $this->parameters = $parameters;
+
+ return $this;
+ }
+
+ /**
+ * Gets a parameter value.
+ *
+ * @param string $name A parameter name
+ *
+ * @return mixed The parameter value or null if nonexistent
+ */
+ public function getParameter($name)
+ {
+ return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
+ }
+
+ /**
+ * Checks if a parameter value is set for the given parameter.
+ *
+ * @param string $name A parameter name
+ *
+ * @return bool True if the parameter value is set, false otherwise
+ */
+ public function hasParameter($name)
+ {
+ return array_key_exists($name, $this->parameters);
+ }
+
+ /**
+ * Sets a parameter value.
+ *
+ * @param string $name A parameter name
+ * @param mixed $parameter The parameter value
+ *
+ * @return $this
+ */
+ public function setParameter($name, $parameter)
+ {
+ $this->parameters[$name] = $parameter;
+
+ return $this;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/RequestContextAwareInterface.php b/assets/php/vendor/symfony/routing/RequestContextAwareInterface.php
new file mode 100644
index 0000000..df5b9fc
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/RequestContextAwareInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+interface RequestContextAwareInterface
+{
+ /**
+ * Sets the request context.
+ */
+ public function setContext(RequestContext $context);
+
+ /**
+ * Gets the request context.
+ *
+ * @return RequestContext The context
+ */
+ public function getContext();
+}
diff --git a/assets/php/vendor/symfony/routing/Route.php b/assets/php/vendor/symfony/routing/Route.php
new file mode 100644
index 0000000..cd50ac8
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Route.php
@@ -0,0 +1,558 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+/**
+ * A Route describes a route and its parameters.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+class Route implements \Serializable
+{
+ private $path = '/';
+ private $host = '';
+ private $schemes = array();
+ private $methods = array();
+ private $defaults = array();
+ private $requirements = array();
+ private $options = array();
+ private $condition = '';
+
+ /**
+ * @var null|CompiledRoute
+ */
+ private $compiled;
+
+ /**
+ * Constructor.
+ *
+ * Available options:
+ *
+ * * compiler_class: A class name able to compile this route instance (RouteCompiler by default)
+ * * utf8: Whether UTF-8 matching is enforced ot not
+ *
+ * @param string $path The path pattern to match
+ * @param array $defaults An array of default parameter values
+ * @param array $requirements An array of requirements for parameters (regexes)
+ * @param array $options An array of options
+ * @param string $host The host pattern to match
+ * @param string|string[] $schemes A required URI scheme or an array of restricted schemes
+ * @param string|string[] $methods A required HTTP method or an array of restricted methods
+ * @param string $condition A condition that should evaluate to true for the route to match
+ */
+ public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '')
+ {
+ $this->setPath($path);
+ $this->setDefaults($defaults);
+ $this->setRequirements($requirements);
+ $this->setOptions($options);
+ $this->setHost($host);
+ $this->setSchemes($schemes);
+ $this->setMethods($methods);
+ $this->setCondition($condition);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function serialize()
+ {
+ return serialize(array(
+ 'path' => $this->path,
+ 'host' => $this->host,
+ 'defaults' => $this->defaults,
+ 'requirements' => $this->requirements,
+ 'options' => $this->options,
+ 'schemes' => $this->schemes,
+ 'methods' => $this->methods,
+ 'condition' => $this->condition,
+ 'compiled' => $this->compiled,
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unserialize($serialized)
+ {
+ $data = unserialize($serialized);
+ $this->path = $data['path'];
+ $this->host = $data['host'];
+ $this->defaults = $data['defaults'];
+ $this->requirements = $data['requirements'];
+ $this->options = $data['options'];
+ $this->schemes = $data['schemes'];
+ $this->methods = $data['methods'];
+
+ if (isset($data['condition'])) {
+ $this->condition = $data['condition'];
+ }
+ if (isset($data['compiled'])) {
+ $this->compiled = $data['compiled'];
+ }
+ }
+
+ /**
+ * Returns the pattern for the path.
+ *
+ * @return string The path pattern
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Sets the pattern for the path.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param string $pattern The path pattern
+ *
+ * @return $this
+ */
+ public function setPath($pattern)
+ {
+ // A pattern must start with a slash and must not have multiple slashes at the beginning because the
+ // generated path for this route would be confused with a network path, e.g. '//domain.com/path'.
+ $this->path = '/'.ltrim(trim($pattern), '/');
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Returns the pattern for the host.
+ *
+ * @return string The host pattern
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Sets the pattern for the host.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param string $pattern The host pattern
+ *
+ * @return $this
+ */
+ public function setHost($pattern)
+ {
+ $this->host = (string) $pattern;
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Returns the lowercased schemes this route is restricted to.
+ * So an empty array means that any scheme is allowed.
+ *
+ * @return string[] The schemes
+ */
+ public function getSchemes()
+ {
+ return $this->schemes;
+ }
+
+ /**
+ * Sets the schemes (e.g. 'https') this route is restricted to.
+ * So an empty array means that any scheme is allowed.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param string|string[] $schemes The scheme or an array of schemes
+ *
+ * @return $this
+ */
+ public function setSchemes($schemes)
+ {
+ $this->schemes = array_map('strtolower', (array) $schemes);
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Checks if a scheme requirement has been set.
+ *
+ * @param string $scheme
+ *
+ * @return bool true if the scheme requirement exists, otherwise false
+ */
+ public function hasScheme($scheme)
+ {
+ return in_array(strtolower($scheme), $this->schemes, true);
+ }
+
+ /**
+ * Returns the uppercased HTTP methods this route is restricted to.
+ * So an empty array means that any method is allowed.
+ *
+ * @return string[] The methods
+ */
+ public function getMethods()
+ {
+ return $this->methods;
+ }
+
+ /**
+ * Sets the HTTP methods (e.g. 'POST') this route is restricted to.
+ * So an empty array means that any method is allowed.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param string|string[] $methods The method or an array of methods
+ *
+ * @return $this
+ */
+ public function setMethods($methods)
+ {
+ $this->methods = array_map('strtoupper', (array) $methods);
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Returns the options.
+ *
+ * @return array The options
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Sets the options.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param array $options The options
+ *
+ * @return $this
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = array(
+ 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler',
+ );
+
+ return $this->addOptions($options);
+ }
+
+ /**
+ * Adds options.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param array $options The options
+ *
+ * @return $this
+ */
+ public function addOptions(array $options)
+ {
+ foreach ($options as $name => $option) {
+ $this->options[$name] = $option;
+ }
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Sets an option value.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param string $name An option name
+ * @param mixed $value The option value
+ *
+ * @return $this
+ */
+ public function setOption($name, $value)
+ {
+ $this->options[$name] = $value;
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Get an option value.
+ *
+ * @param string $name An option name
+ *
+ * @return mixed The option value or null when not given
+ */
+ public function getOption($name)
+ {
+ return isset($this->options[$name]) ? $this->options[$name] : null;
+ }
+
+ /**
+ * Checks if an option has been set.
+ *
+ * @param string $name An option name
+ *
+ * @return bool true if the option is set, false otherwise
+ */
+ public function hasOption($name)
+ {
+ return array_key_exists($name, $this->options);
+ }
+
+ /**
+ * Returns the defaults.
+ *
+ * @return array The defaults
+ */
+ public function getDefaults()
+ {
+ return $this->defaults;
+ }
+
+ /**
+ * Sets the defaults.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param array $defaults The defaults
+ *
+ * @return $this
+ */
+ public function setDefaults(array $defaults)
+ {
+ $this->defaults = array();
+
+ return $this->addDefaults($defaults);
+ }
+
+ /**
+ * Adds defaults.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param array $defaults The defaults
+ *
+ * @return $this
+ */
+ public function addDefaults(array $defaults)
+ {
+ foreach ($defaults as $name => $default) {
+ $this->defaults[$name] = $default;
+ }
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Gets a default value.
+ *
+ * @param string $name A variable name
+ *
+ * @return mixed The default value or null when not given
+ */
+ public function getDefault($name)
+ {
+ return isset($this->defaults[$name]) ? $this->defaults[$name] : null;
+ }
+
+ /**
+ * Checks if a default value is set for the given variable.
+ *
+ * @param string $name A variable name
+ *
+ * @return bool true if the default value is set, false otherwise
+ */
+ public function hasDefault($name)
+ {
+ return array_key_exists($name, $this->defaults);
+ }
+
+ /**
+ * Sets a default value.
+ *
+ * @param string $name A variable name
+ * @param mixed $default The default value
+ *
+ * @return $this
+ */
+ public function setDefault($name, $default)
+ {
+ $this->defaults[$name] = $default;
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Returns the requirements.
+ *
+ * @return array The requirements
+ */
+ public function getRequirements()
+ {
+ return $this->requirements;
+ }
+
+ /**
+ * Sets the requirements.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param array $requirements The requirements
+ *
+ * @return $this
+ */
+ public function setRequirements(array $requirements)
+ {
+ $this->requirements = array();
+
+ return $this->addRequirements($requirements);
+ }
+
+ /**
+ * Adds requirements.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param array $requirements The requirements
+ *
+ * @return $this
+ */
+ public function addRequirements(array $requirements)
+ {
+ foreach ($requirements as $key => $regex) {
+ $this->requirements[$key] = $this->sanitizeRequirement($key, $regex);
+ }
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Returns the requirement for the given key.
+ *
+ * @param string $key The key
+ *
+ * @return string|null The regex or null when not given
+ */
+ public function getRequirement($key)
+ {
+ return isset($this->requirements[$key]) ? $this->requirements[$key] : null;
+ }
+
+ /**
+ * Checks if a requirement is set for the given key.
+ *
+ * @param string $key A variable name
+ *
+ * @return bool true if a requirement is specified, false otherwise
+ */
+ public function hasRequirement($key)
+ {
+ return array_key_exists($key, $this->requirements);
+ }
+
+ /**
+ * Sets a requirement for the given key.
+ *
+ * @param string $key The key
+ * @param string $regex The regex
+ *
+ * @return $this
+ */
+ public function setRequirement($key, $regex)
+ {
+ $this->requirements[$key] = $this->sanitizeRequirement($key, $regex);
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Returns the condition.
+ *
+ * @return string The condition
+ */
+ public function getCondition()
+ {
+ return $this->condition;
+ }
+
+ /**
+ * Sets the condition.
+ *
+ * This method implements a fluent interface.
+ *
+ * @param string $condition The condition
+ *
+ * @return $this
+ */
+ public function setCondition($condition)
+ {
+ $this->condition = (string) $condition;
+ $this->compiled = null;
+
+ return $this;
+ }
+
+ /**
+ * Compiles the route.
+ *
+ * @return CompiledRoute A CompiledRoute instance
+ *
+ * @throws \LogicException If the Route cannot be compiled because the
+ * path or host pattern is invalid
+ *
+ * @see RouteCompiler which is responsible for the compilation process
+ */
+ public function compile()
+ {
+ if (null !== $this->compiled) {
+ return $this->compiled;
+ }
+
+ $class = $this->getOption('compiler_class');
+
+ return $this->compiled = $class::compile($this);
+ }
+
+ private function sanitizeRequirement($key, $regex)
+ {
+ if (!is_string($regex)) {
+ throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key));
+ }
+
+ if ('' !== $regex && '^' === $regex[0]) {
+ $regex = (string) substr($regex, 1); // returns false for a single character
+ }
+
+ if ('$' === substr($regex, -1)) {
+ $regex = substr($regex, 0, -1);
+ }
+
+ if ('' === $regex) {
+ throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key));
+ }
+
+ return $regex;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/RouteCollection.php b/assets/php/vendor/symfony/routing/RouteCollection.php
new file mode 100644
index 0000000..e22cbc5
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/RouteCollection.php
@@ -0,0 +1,280 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+
+/**
+ * A RouteCollection represents a set of Route instances.
+ *
+ * When adding a route at the end of the collection, an existing route
+ * with the same name is removed first. So there can only be one route
+ * with a given name.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+class RouteCollection implements \IteratorAggregate, \Countable
+{
+ /**
+ * @var Route[]
+ */
+ private $routes = array();
+
+ /**
+ * @var array
+ */
+ private $resources = array();
+
+ public function __clone()
+ {
+ foreach ($this->routes as $name => $route) {
+ $this->routes[$name] = clone $route;
+ }
+ }
+
+ /**
+ * Gets the current RouteCollection as an Iterator that includes all routes.
+ *
+ * It implements \IteratorAggregate.
+ *
+ * @see all()
+ *
+ * @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->routes);
+ }
+
+ /**
+ * Gets the number of Routes in this collection.
+ *
+ * @return int The number of routes
+ */
+ public function count()
+ {
+ return count($this->routes);
+ }
+
+ /**
+ * Adds a route.
+ *
+ * @param string $name The route name
+ * @param Route $route A Route instance
+ */
+ public function add($name, Route $route)
+ {
+ unset($this->routes[$name]);
+
+ $this->routes[$name] = $route;
+ }
+
+ /**
+ * Returns all routes in this collection.
+ *
+ * @return Route[] An array of routes
+ */
+ public function all()
+ {
+ return $this->routes;
+ }
+
+ /**
+ * Gets a route by name.
+ *
+ * @param string $name The route name
+ *
+ * @return Route|null A Route instance or null when not found
+ */
+ public function get($name)
+ {
+ return isset($this->routes[$name]) ? $this->routes[$name] : null;
+ }
+
+ /**
+ * Removes a route or an array of routes by name from the collection.
+ *
+ * @param string|string[] $name The route name or an array of route names
+ */
+ public function remove($name)
+ {
+ foreach ((array) $name as $n) {
+ unset($this->routes[$n]);
+ }
+ }
+
+ /**
+ * Adds a route collection at the end of the current set by appending all
+ * routes of the added collection.
+ */
+ public function addCollection(self $collection)
+ {
+ // we need to remove all routes with the same names first because just replacing them
+ // would not place the new route at the end of the merged array
+ foreach ($collection->all() as $name => $route) {
+ unset($this->routes[$name]);
+ $this->routes[$name] = $route;
+ }
+
+ foreach ($collection->getResources() as $resource) {
+ $this->addResource($resource);
+ }
+ }
+
+ /**
+ * Adds a prefix to the path of all child routes.
+ *
+ * @param string $prefix An optional prefix to add before each pattern of the route collection
+ * @param array $defaults An array of default values
+ * @param array $requirements An array of requirements
+ */
+ public function addPrefix($prefix, array $defaults = array(), array $requirements = array())
+ {
+ $prefix = trim(trim($prefix), '/');
+
+ if ('' === $prefix) {
+ return;
+ }
+
+ foreach ($this->routes as $route) {
+ $route->setPath('/'.$prefix.$route->getPath());
+ $route->addDefaults($defaults);
+ $route->addRequirements($requirements);
+ }
+ }
+
+ /**
+ * Sets the host pattern on all routes.
+ *
+ * @param string $pattern The pattern
+ * @param array $defaults An array of default values
+ * @param array $requirements An array of requirements
+ */
+ public function setHost($pattern, array $defaults = array(), array $requirements = array())
+ {
+ foreach ($this->routes as $route) {
+ $route->setHost($pattern);
+ $route->addDefaults($defaults);
+ $route->addRequirements($requirements);
+ }
+ }
+
+ /**
+ * Sets a condition on all routes.
+ *
+ * Existing conditions will be overridden.
+ *
+ * @param string $condition The condition
+ */
+ public function setCondition($condition)
+ {
+ foreach ($this->routes as $route) {
+ $route->setCondition($condition);
+ }
+ }
+
+ /**
+ * Adds defaults to all routes.
+ *
+ * An existing default value under the same name in a route will be overridden.
+ *
+ * @param array $defaults An array of default values
+ */
+ public function addDefaults(array $defaults)
+ {
+ if ($defaults) {
+ foreach ($this->routes as $route) {
+ $route->addDefaults($defaults);
+ }
+ }
+ }
+
+ /**
+ * Adds requirements to all routes.
+ *
+ * An existing requirement under the same name in a route will be overridden.
+ *
+ * @param array $requirements An array of requirements
+ */
+ public function addRequirements(array $requirements)
+ {
+ if ($requirements) {
+ foreach ($this->routes as $route) {
+ $route->addRequirements($requirements);
+ }
+ }
+ }
+
+ /**
+ * Adds options to all routes.
+ *
+ * An existing option value under the same name in a route will be overridden.
+ *
+ * @param array $options An array of options
+ */
+ public function addOptions(array $options)
+ {
+ if ($options) {
+ foreach ($this->routes as $route) {
+ $route->addOptions($options);
+ }
+ }
+ }
+
+ /**
+ * Sets the schemes (e.g. 'https') all child routes are restricted to.
+ *
+ * @param string|string[] $schemes The scheme or an array of schemes
+ */
+ public function setSchemes($schemes)
+ {
+ foreach ($this->routes as $route) {
+ $route->setSchemes($schemes);
+ }
+ }
+
+ /**
+ * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to.
+ *
+ * @param string|string[] $methods The method or an array of methods
+ */
+ public function setMethods($methods)
+ {
+ foreach ($this->routes as $route) {
+ $route->setMethods($methods);
+ }
+ }
+
+ /**
+ * Returns an array of resources loaded to build this collection.
+ *
+ * @return ResourceInterface[] An array of resources
+ */
+ public function getResources()
+ {
+ return array_values($this->resources);
+ }
+
+ /**
+ * Adds a resource for this collection. If the resource already exists
+ * it is not added.
+ */
+ public function addResource(ResourceInterface $resource)
+ {
+ $key = (string) $resource;
+
+ if (!isset($this->resources[$key])) {
+ $this->resources[$key] = $resource;
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/RouteCollectionBuilder.php b/assets/php/vendor/symfony/routing/RouteCollectionBuilder.php
new file mode 100644
index 0000000..e8a9a16
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/RouteCollectionBuilder.php
@@ -0,0 +1,380 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+use Symfony\Component\Config\Exception\FileLoaderLoadException;
+use Symfony\Component\Config\Loader\LoaderInterface;
+use Symfony\Component\Config\Resource\ResourceInterface;
+
+/**
+ * Helps add and import routes into a RouteCollection.
+ *
+ * @author Ryan Weaver
+ */
+class RouteCollectionBuilder
+{
+ /**
+ * @var Route[]|RouteCollectionBuilder[]
+ */
+ private $routes = array();
+
+ private $loader;
+ private $defaults = array();
+ private $prefix;
+ private $host;
+ private $condition;
+ private $requirements = array();
+ private $options = array();
+ private $schemes;
+ private $methods;
+ private $resources = array();
+
+ public function __construct(LoaderInterface $loader = null)
+ {
+ $this->loader = $loader;
+ }
+
+ /**
+ * Import an external routing resource and returns the RouteCollectionBuilder.
+ *
+ * $routes->import('blog.yml', '/blog');
+ *
+ * @param mixed $resource
+ * @param string|null $prefix
+ * @param string $type
+ *
+ * @return self
+ *
+ * @throws FileLoaderLoadException
+ */
+ public function import($resource, $prefix = '/', $type = null)
+ {
+ /** @var RouteCollection[] $collection */
+ $collections = $this->load($resource, $type);
+
+ // create a builder from the RouteCollection
+ $builder = $this->createBuilder();
+
+ foreach ($collections as $collection) {
+ if (null === $collection) {
+ continue;
+ }
+
+ foreach ($collection->all() as $name => $route) {
+ $builder->addRoute($route, $name);
+ }
+
+ foreach ($collection->getResources() as $resource) {
+ $builder->addResource($resource);
+ }
+ }
+
+ // mount into this builder
+ $this->mount($prefix, $builder);
+
+ return $builder;
+ }
+
+ /**
+ * Adds a route and returns it for future modification.
+ *
+ * @param string $path The route path
+ * @param string $controller The route's controller
+ * @param string|null $name The name to give this route
+ *
+ * @return Route
+ */
+ public function add($path, $controller, $name = null)
+ {
+ $route = new Route($path);
+ $route->setDefault('_controller', $controller);
+ $this->addRoute($route, $name);
+
+ return $route;
+ }
+
+ /**
+ * Returns a RouteCollectionBuilder that can be configured and then added with mount().
+ *
+ * @return self
+ */
+ public function createBuilder()
+ {
+ return new self($this->loader);
+ }
+
+ /**
+ * Add a RouteCollectionBuilder.
+ *
+ * @param string $prefix
+ * @param RouteCollectionBuilder $builder
+ */
+ public function mount($prefix, RouteCollectionBuilder $builder)
+ {
+ $builder->prefix = trim(trim($prefix), '/');
+ $this->routes[] = $builder;
+ }
+
+ /**
+ * Adds a Route object to the builder.
+ *
+ * @param Route $route
+ * @param string|null $name
+ *
+ * @return $this
+ */
+ public function addRoute(Route $route, $name = null)
+ {
+ if (null === $name) {
+ // used as a flag to know which routes will need a name later
+ $name = '_unnamed_route_'.spl_object_hash($route);
+ }
+
+ $this->routes[$name] = $route;
+
+ return $this;
+ }
+
+ /**
+ * Sets the host on all embedded routes (unless already set).
+ *
+ * @param string $pattern
+ *
+ * @return $this
+ */
+ public function setHost($pattern)
+ {
+ $this->host = $pattern;
+
+ return $this;
+ }
+
+ /**
+ * Sets a condition on all embedded routes (unless already set).
+ *
+ * @param string $condition
+ *
+ * @return $this
+ */
+ public function setCondition($condition)
+ {
+ $this->condition = $condition;
+
+ return $this;
+ }
+
+ /**
+ * Sets a default value that will be added to all embedded routes (unless that
+ * default value is already set).
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function setDefault($key, $value)
+ {
+ $this->defaults[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets a requirement that will be added to all embedded routes (unless that
+ * requirement is already set).
+ *
+ * @param string $key
+ * @param mixed $regex
+ *
+ * @return $this
+ */
+ public function setRequirement($key, $regex)
+ {
+ $this->requirements[$key] = $regex;
+
+ return $this;
+ }
+
+ /**
+ * Sets an option that will be added to all embedded routes (unless that
+ * option is already set).
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function setOption($key, $value)
+ {
+ $this->options[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the schemes on all embedded routes (unless already set).
+ *
+ * @param array|string $schemes
+ *
+ * @return $this
+ */
+ public function setSchemes($schemes)
+ {
+ $this->schemes = $schemes;
+
+ return $this;
+ }
+
+ /**
+ * Sets the methods on all embedded routes (unless already set).
+ *
+ * @param array|string $methods
+ *
+ * @return $this
+ */
+ public function setMethods($methods)
+ {
+ $this->methods = $methods;
+
+ return $this;
+ }
+
+ /**
+ * Adds a resource for this collection.
+ *
+ * @param ResourceInterface $resource
+ *
+ * @return $this
+ */
+ private function addResource(ResourceInterface $resource)
+ {
+ $this->resources[] = $resource;
+
+ return $this;
+ }
+
+ /**
+ * Creates the final RouteCollection and returns it.
+ *
+ * @return RouteCollection
+ */
+ public function build()
+ {
+ $routeCollection = new RouteCollection();
+
+ foreach ($this->routes as $name => $route) {
+ if ($route instanceof Route) {
+ $route->setDefaults(array_merge($this->defaults, $route->getDefaults()));
+ $route->setOptions(array_merge($this->options, $route->getOptions()));
+
+ foreach ($this->requirements as $key => $val) {
+ if (!$route->hasRequirement($key)) {
+ $route->setRequirement($key, $val);
+ }
+ }
+
+ if (null !== $this->prefix) {
+ $route->setPath('/'.$this->prefix.$route->getPath());
+ }
+
+ if (!$route->getHost()) {
+ $route->setHost($this->host);
+ }
+
+ if (!$route->getCondition()) {
+ $route->setCondition($this->condition);
+ }
+
+ if (!$route->getSchemes()) {
+ $route->setSchemes($this->schemes);
+ }
+
+ if (!$route->getMethods()) {
+ $route->setMethods($this->methods);
+ }
+
+ // auto-generate the route name if it's been marked
+ if ('_unnamed_route_' === substr($name, 0, 15)) {
+ $name = $this->generateRouteName($route);
+ }
+
+ $routeCollection->add($name, $route);
+ } else {
+ /* @var self $route */
+ $subCollection = $route->build();
+ $subCollection->addPrefix($this->prefix);
+
+ $routeCollection->addCollection($subCollection);
+ }
+ }
+
+ foreach ($this->resources as $resource) {
+ $routeCollection->addResource($resource);
+ }
+
+ return $routeCollection;
+ }
+
+ /**
+ * Generates a route name based on details of this route.
+ *
+ * @return string
+ */
+ private function generateRouteName(Route $route)
+ {
+ $methods = implode('_', $route->getMethods()).'_';
+
+ $routeName = $methods.$route->getPath();
+ $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName);
+ $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);
+
+ // Collapse consecutive underscores down into a single underscore.
+ $routeName = preg_replace('/_+/', '_', $routeName);
+
+ return $routeName;
+ }
+
+ /**
+ * Finds a loader able to load an imported resource and loads it.
+ *
+ * @param mixed $resource A resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @return RouteCollection[]
+ *
+ * @throws FileLoaderLoadException If no loader is found
+ */
+ private function load($resource, $type = null)
+ {
+ if (null === $this->loader) {
+ throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.');
+ }
+
+ if ($this->loader->supports($resource, $type)) {
+ $collections = $this->loader->load($resource, $type);
+
+ return is_array($collections) ? $collections : array($collections);
+ }
+
+ if (null === $resolver = $this->loader->getResolver()) {
+ throw new FileLoaderLoadException($resource, null, null, null, $type);
+ }
+
+ if (false === $loader = $resolver->resolve($resource, $type)) {
+ throw new FileLoaderLoadException($resource, null, null, null, $type);
+ }
+
+ $collections = $loader->load($resource, $type);
+
+ return is_array($collections) ? $collections : array($collections);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/RouteCompiler.php b/assets/php/vendor/symfony/routing/RouteCompiler.php
new file mode 100644
index 0000000..dc4e4f8
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/RouteCompiler.php
@@ -0,0 +1,316 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+/**
+ * RouteCompiler compiles Route instances to CompiledRoute instances.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ */
+class RouteCompiler implements RouteCompilerInterface
+{
+ const REGEX_DELIMITER = '#';
+
+ /**
+ * This string defines the characters that are automatically considered separators in front of
+ * optional placeholders (with default and no static text following). Such a single separator
+ * can be left out together with the optional placeholder from matching and generating URLs.
+ */
+ const SEPARATORS = '/,;.:-_~+*=@|';
+
+ /**
+ * The maximum supported length of a PCRE subpattern name
+ * http://pcre.org/current/doc/html/pcre2pattern.html#SEC16.
+ *
+ * @internal
+ */
+ const VARIABLE_MAXIMUM_LENGTH = 32;
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \InvalidArgumentException if a path variable is named _fragment
+ * @throws \LogicException if a variable is referenced more than once
+ * @throws \DomainException if a variable name starts with a digit or if it is too long to be successfully used as
+ * a PCRE subpattern
+ */
+ public static function compile(Route $route)
+ {
+ $hostVariables = array();
+ $variables = array();
+ $hostRegex = null;
+ $hostTokens = array();
+
+ if ('' !== $host = $route->getHost()) {
+ $result = self::compilePattern($route, $host, true);
+
+ $hostVariables = $result['variables'];
+ $variables = $hostVariables;
+
+ $hostTokens = $result['tokens'];
+ $hostRegex = $result['regex'];
+ }
+
+ $path = $route->getPath();
+
+ $result = self::compilePattern($route, $path, false);
+
+ $staticPrefix = $result['staticPrefix'];
+
+ $pathVariables = $result['variables'];
+
+ foreach ($pathVariables as $pathParam) {
+ if ('_fragment' === $pathParam) {
+ throw new \InvalidArgumentException(sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath()));
+ }
+ }
+
+ $variables = array_merge($variables, $pathVariables);
+
+ $tokens = $result['tokens'];
+ $regex = $result['regex'];
+
+ return new CompiledRoute(
+ $staticPrefix,
+ $regex,
+ $tokens,
+ $pathVariables,
+ $hostRegex,
+ $hostTokens,
+ $hostVariables,
+ array_unique($variables)
+ );
+ }
+
+ private static function compilePattern(Route $route, $pattern, $isHost)
+ {
+ $tokens = array();
+ $variables = array();
+ $matches = array();
+ $pos = 0;
+ $defaultSeparator = $isHost ? '.' : '/';
+ $useUtf8 = preg_match('//u', $pattern);
+ $needsUtf8 = $route->getOption('utf8');
+
+ if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) {
+ $needsUtf8 = true;
+ @trigger_error(sprintf('Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "%s".', $pattern), E_USER_DEPRECATED);
+ }
+ if (!$useUtf8 && $needsUtf8) {
+ throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern));
+ }
+
+ // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
+ // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself.
+ preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ $varName = substr($match[0][0], 1, -1);
+ // get all static text preceding the current variable
+ $precedingText = substr($pattern, $pos, $match[0][1] - $pos);
+ $pos = $match[0][1] + strlen($match[0][0]);
+
+ if (!strlen($precedingText)) {
+ $precedingChar = '';
+ } elseif ($useUtf8) {
+ preg_match('/.$/u', $precedingText, $precedingChar);
+ $precedingChar = $precedingChar[0];
+ } else {
+ $precedingChar = substr($precedingText, -1);
+ }
+ $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar);
+
+ // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the
+ // variable would not be usable as a Controller action argument.
+ if (preg_match('/^\d/', $varName)) {
+ throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern));
+ }
+ if (in_array($varName, $variables)) {
+ throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName));
+ }
+
+ if (strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) {
+ throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %s characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern));
+ }
+
+ if ($isSeparator && $precedingText !== $precedingChar) {
+ $tokens[] = array('text', substr($precedingText, 0, -strlen($precedingChar)));
+ } elseif (!$isSeparator && strlen($precedingText) > 0) {
+ $tokens[] = array('text', $precedingText);
+ }
+
+ $regexp = $route->getRequirement($varName);
+ if (null === $regexp) {
+ $followingPattern = (string) substr($pattern, $pos);
+ // Find the next static character after the variable that functions as a separator. By default, this separator and '/'
+ // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all
+ // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are
+ // the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html'))
+ // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything.
+ // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally
+ // part of {_format} when generating the URL, e.g. _format = 'mobile.html'.
+ $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8);
+ $regexp = sprintf(
+ '[^%s%s]+',
+ preg_quote($defaultSeparator, self::REGEX_DELIMITER),
+ $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : ''
+ );
+ if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
+ // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive
+ // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns.
+ // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow
+ // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is
+ // directly adjacent, e.g. '/{x}{y}'.
+ $regexp .= '+';
+ }
+ } else {
+ if (!preg_match('//u', $regexp)) {
+ $useUtf8 = false;
+ } elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?= 0; --$i) {
+ $token = $tokens[$i];
+ if ('variable' === $token[0] && $route->hasDefault($token[3])) {
+ $firstOptional = $i;
+ } else {
+ break;
+ }
+ }
+ }
+
+ // compute the matching regexp
+ $regexp = '';
+ for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) {
+ $regexp .= self::computeRegexp($tokens, $i, $firstOptional);
+ }
+ $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'sD'.($isHost ? 'i' : '');
+
+ // enable Utf8 matching if really required
+ if ($needsUtf8) {
+ $regexp .= 'u';
+ for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) {
+ if ('variable' === $tokens[$i][0]) {
+ $tokens[$i][] = true;
+ }
+ }
+ }
+
+ return array(
+ 'staticPrefix' => self::determineStaticPrefix($route, $tokens),
+ 'regex' => $regexp,
+ 'tokens' => array_reverse($tokens),
+ 'variables' => $variables,
+ );
+ }
+
+ /**
+ * Determines the longest static prefix possible for a route.
+ *
+ * @return string The leading static part of a route's path
+ */
+ private static function determineStaticPrefix(Route $route, array $tokens)
+ {
+ if ('text' !== $tokens[0][0]) {
+ return ($route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1]) ? '' : $tokens[0][1];
+ }
+
+ $prefix = $tokens[0][1];
+
+ if (isset($tokens[1][1]) && '/' !== $tokens[1][1] && false === $route->hasDefault($tokens[1][3])) {
+ $prefix .= $tokens[1][1];
+ }
+
+ return $prefix;
+ }
+
+ /**
+ * Returns the next static character in the Route pattern that will serve as a separator.
+ *
+ * @param string $pattern The route pattern
+ * @param bool $useUtf8 Whether the character is encoded in UTF-8 or not
+ *
+ * @return string The next static character that functions as separator (or empty string when none available)
+ */
+ private static function findNextSeparator($pattern, $useUtf8)
+ {
+ if ('' == $pattern) {
+ // return empty string if pattern is empty or false (false which can be returned by substr)
+ return '';
+ }
+ // first remove all placeholders from the pattern so we can find the next real static character
+ if ('' === $pattern = preg_replace('#\{\w+\}#', '', $pattern)) {
+ return '';
+ }
+ if ($useUtf8) {
+ preg_match('/^./u', $pattern, $pattern);
+ }
+
+ return false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : '';
+ }
+
+ /**
+ * Computes the regexp used to match a specific token. It can be static text or a subpattern.
+ *
+ * @param array $tokens The route tokens
+ * @param int $index The index of the current token
+ * @param int $firstOptional The index of the first optional token
+ *
+ * @return string The regexp pattern for a single token
+ */
+ private static function computeRegexp(array $tokens, $index, $firstOptional)
+ {
+ $token = $tokens[$index];
+ if ('text' === $token[0]) {
+ // Text tokens
+ return preg_quote($token[1], self::REGEX_DELIMITER);
+ } else {
+ // Variable tokens
+ if (0 === $index && 0 === $firstOptional) {
+ // When the only token is an optional variable token, the separator is required
+ return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]);
+ } else {
+ $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]);
+ if ($index >= $firstOptional) {
+ // Enclose each optional token in a subpattern to make it optional.
+ // "?:" means it is non-capturing, i.e. the portion of the subject string that
+ // matched the optional subpattern is not passed back.
+ $regexp = "(?:$regexp";
+ $nbTokens = count($tokens);
+ if ($nbTokens - 1 == $index) {
+ // Close the optional subpatterns
+ $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0));
+ }
+ }
+
+ return $regexp;
+ }
+ }
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/RouteCompilerInterface.php b/assets/php/vendor/symfony/routing/RouteCompilerInterface.php
new file mode 100644
index 0000000..ddfa7ca
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/RouteCompilerInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+/**
+ * RouteCompilerInterface is the interface that all RouteCompiler classes must implement.
+ *
+ * @author Fabien Potencier
+ */
+interface RouteCompilerInterface
+{
+ /**
+ * Compiles the current route instance.
+ *
+ * @return CompiledRoute A CompiledRoute instance
+ *
+ * @throws \LogicException If the Route cannot be compiled because the
+ * path or host pattern is invalid
+ */
+ public static function compile(Route $route);
+}
diff --git a/assets/php/vendor/symfony/routing/Router.php b/assets/php/vendor/symfony/routing/Router.php
new file mode 100644
index 0000000..ed56332
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Router.php
@@ -0,0 +1,388 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+use Symfony\Component\Config\Loader\LoaderInterface;
+use Symfony\Component\Config\ConfigCacheInterface;
+use Symfony\Component\Config\ConfigCacheFactoryInterface;
+use Symfony\Component\Config\ConfigCacheFactory;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface;
+use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
+use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
+
+/**
+ * The Router class is an example of the integration of all pieces of the
+ * routing system for easier use.
+ *
+ * @author Fabien Potencier
+ */
+class Router implements RouterInterface, RequestMatcherInterface
+{
+ /**
+ * @var UrlMatcherInterface|null
+ */
+ protected $matcher;
+
+ /**
+ * @var UrlGeneratorInterface|null
+ */
+ protected $generator;
+
+ /**
+ * @var RequestContext
+ */
+ protected $context;
+
+ /**
+ * @var LoaderInterface
+ */
+ protected $loader;
+
+ /**
+ * @var RouteCollection|null
+ */
+ protected $collection;
+
+ /**
+ * @var mixed
+ */
+ protected $resource;
+
+ /**
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * @var LoggerInterface|null
+ */
+ protected $logger;
+
+ /**
+ * @var ConfigCacheFactoryInterface|null
+ */
+ private $configCacheFactory;
+
+ /**
+ * @var ExpressionFunctionProviderInterface[]
+ */
+ private $expressionLanguageProviders = array();
+
+ /**
+ * @param LoaderInterface $loader A LoaderInterface instance
+ * @param mixed $resource The main resource to load
+ * @param array $options An array of options
+ * @param RequestContext $context The context
+ * @param LoggerInterface $logger A logger instance
+ */
+ public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, LoggerInterface $logger = null)
+ {
+ $this->loader = $loader;
+ $this->resource = $resource;
+ $this->logger = $logger;
+ $this->context = $context ?: new RequestContext();
+ $this->setOptions($options);
+ }
+
+ /**
+ * Sets options.
+ *
+ * Available options:
+ *
+ * * cache_dir: The cache directory (or null to disable caching)
+ * * debug: Whether to enable debugging or not (false by default)
+ * * generator_class: The name of a UrlGeneratorInterface implementation
+ * * generator_base_class: The base class for the dumped generator class
+ * * generator_cache_class: The class name for the dumped generator class
+ * * generator_dumper_class: The name of a GeneratorDumperInterface implementation
+ * * matcher_class: The name of a UrlMatcherInterface implementation
+ * * matcher_base_class: The base class for the dumped matcher class
+ * * matcher_dumper_class: The class name for the dumped matcher class
+ * * matcher_cache_class: The name of a MatcherDumperInterface implementation
+ * * resource_type: Type hint for the main resource (optional)
+ * * strict_requirements: Configure strict requirement checking for generators
+ * implementing ConfigurableRequirementsInterface (default is true)
+ *
+ * @param array $options An array of options
+ *
+ * @throws \InvalidArgumentException When unsupported option is provided
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = array(
+ 'cache_dir' => null,
+ 'debug' => false,
+ 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
+ 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
+ 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper',
+ 'generator_cache_class' => 'ProjectUrlGenerator',
+ 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
+ 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
+ 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper',
+ 'matcher_cache_class' => 'ProjectUrlMatcher',
+ 'resource_type' => null,
+ 'strict_requirements' => true,
+ );
+
+ // check option names and live merge, if errors are encountered Exception will be thrown
+ $invalid = array();
+ foreach ($options as $key => $value) {
+ if (array_key_exists($key, $this->options)) {
+ $this->options[$key] = $value;
+ } else {
+ $invalid[] = $key;
+ }
+ }
+
+ if ($invalid) {
+ throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid)));
+ }
+ }
+
+ /**
+ * Sets an option.
+ *
+ * @param string $key The key
+ * @param mixed $value The value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setOption($key, $value)
+ {
+ if (!array_key_exists($key, $this->options)) {
+ throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key));
+ }
+
+ $this->options[$key] = $value;
+ }
+
+ /**
+ * Gets an option value.
+ *
+ * @param string $key The key
+ *
+ * @return mixed The value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function getOption($key)
+ {
+ if (!array_key_exists($key, $this->options)) {
+ throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key));
+ }
+
+ return $this->options[$key];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRouteCollection()
+ {
+ if (null === $this->collection) {
+ $this->collection = $this->loader->load($this->resource, $this->options['resource_type']);
+ }
+
+ return $this->collection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setContext(RequestContext $context)
+ {
+ $this->context = $context;
+
+ if (null !== $this->matcher) {
+ $this->getMatcher()->setContext($context);
+ }
+ if (null !== $this->generator) {
+ $this->getGenerator()->setContext($context);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ /**
+ * Sets the ConfigCache factory to use.
+ */
+ public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
+ {
+ $this->configCacheFactory = $configCacheFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
+ {
+ return $this->getGenerator()->generate($name, $parameters, $referenceType);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function match($pathinfo)
+ {
+ return $this->getMatcher()->match($pathinfo);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function matchRequest(Request $request)
+ {
+ $matcher = $this->getMatcher();
+ if (!$matcher instanceof RequestMatcherInterface) {
+ // fallback to the default UrlMatcherInterface
+ return $matcher->match($request->getPathInfo());
+ }
+
+ return $matcher->matchRequest($request);
+ }
+
+ /**
+ * Gets the UrlMatcher instance associated with this Router.
+ *
+ * @return UrlMatcherInterface A UrlMatcherInterface instance
+ */
+ public function getMatcher()
+ {
+ if (null !== $this->matcher) {
+ return $this->matcher;
+ }
+
+ if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) {
+ $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context);
+ if (method_exists($this->matcher, 'addExpressionLanguageProvider')) {
+ foreach ($this->expressionLanguageProviders as $provider) {
+ $this->matcher->addExpressionLanguageProvider($provider);
+ }
+ }
+
+ return $this->matcher;
+ }
+
+ $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['matcher_cache_class'].'.php',
+ function (ConfigCacheInterface $cache) {
+ $dumper = $this->getMatcherDumperInstance();
+ if (method_exists($dumper, 'addExpressionLanguageProvider')) {
+ foreach ($this->expressionLanguageProviders as $provider) {
+ $dumper->addExpressionLanguageProvider($provider);
+ }
+ }
+
+ $options = array(
+ 'class' => $this->options['matcher_cache_class'],
+ 'base_class' => $this->options['matcher_base_class'],
+ );
+
+ $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources());
+ }
+ );
+
+ if (!class_exists($this->options['matcher_cache_class'], false)) {
+ require_once $cache->getPath();
+ }
+
+ return $this->matcher = new $this->options['matcher_cache_class']($this->context);
+ }
+
+ /**
+ * Gets the UrlGenerator instance associated with this Router.
+ *
+ * @return UrlGeneratorInterface A UrlGeneratorInterface instance
+ */
+ public function getGenerator()
+ {
+ if (null !== $this->generator) {
+ return $this->generator;
+ }
+
+ if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) {
+ $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger);
+ } else {
+ $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php',
+ function (ConfigCacheInterface $cache) {
+ $dumper = $this->getGeneratorDumperInstance();
+
+ $options = array(
+ 'class' => $this->options['generator_cache_class'],
+ 'base_class' => $this->options['generator_base_class'],
+ );
+
+ $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources());
+ }
+ );
+
+ if (!class_exists($this->options['generator_cache_class'], false)) {
+ require_once $cache->getPath();
+ }
+
+ $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger);
+ }
+
+ if ($this->generator instanceof ConfigurableRequirementsInterface) {
+ $this->generator->setStrictRequirements($this->options['strict_requirements']);
+ }
+
+ return $this->generator;
+ }
+
+ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
+ {
+ $this->expressionLanguageProviders[] = $provider;
+ }
+
+ /**
+ * @return GeneratorDumperInterface
+ */
+ protected function getGeneratorDumperInstance()
+ {
+ return new $this->options['generator_dumper_class']($this->getRouteCollection());
+ }
+
+ /**
+ * @return MatcherDumperInterface
+ */
+ protected function getMatcherDumperInstance()
+ {
+ return new $this->options['matcher_dumper_class']($this->getRouteCollection());
+ }
+
+ /**
+ * Provides the ConfigCache factory implementation, falling back to a
+ * default implementation if necessary.
+ *
+ * @return ConfigCacheFactoryInterface $configCacheFactory
+ */
+ private function getConfigCacheFactory()
+ {
+ if (null === $this->configCacheFactory) {
+ $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']);
+ }
+
+ return $this->configCacheFactory;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/RouterInterface.php b/assets/php/vendor/symfony/routing/RouterInterface.php
new file mode 100644
index 0000000..a10ae34
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/RouterInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing;
+
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
+
+/**
+ * RouterInterface is the interface that all Router classes must implement.
+ *
+ * This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface.
+ *
+ * @author Fabien Potencier
+ */
+interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface
+{
+ /**
+ * Gets the RouteCollection instance associated with this Router.
+ *
+ * @return RouteCollection A RouteCollection instance
+ */
+ public function getRouteCollection();
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Annotation/RouteTest.php b/assets/php/vendor/symfony/routing/Tests/Annotation/RouteTest.php
new file mode 100644
index 0000000..9af22f2
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Annotation/RouteTest.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Annotation;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Annotation\Route;
+
+class RouteTest extends TestCase
+{
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testInvalidRouteParameter()
+ {
+ $route = new Route(array('foo' => 'bar'));
+ }
+
+ /**
+ * @dataProvider getValidParameters
+ */
+ public function testRouteParameters($parameter, $value, $getter)
+ {
+ $route = new Route(array($parameter => $value));
+ $this->assertEquals($route->$getter(), $value);
+ }
+
+ public function getValidParameters()
+ {
+ return array(
+ array('value', '/Blog', 'getPath'),
+ array('requirements', array('locale' => 'en'), 'getRequirements'),
+ array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'),
+ array('name', 'blog_index', 'getName'),
+ array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'),
+ array('schemes', array('https'), 'getSchemes'),
+ array('methods', array('GET', 'POST'), 'getMethods'),
+ array('host', '{locale}.example.com', 'getHost'),
+ array('condition', 'context.getMethod() == "GET"', 'getCondition'),
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/CompiledRouteTest.php b/assets/php/vendor/symfony/routing/Tests/CompiledRouteTest.php
new file mode 100644
index 0000000..5bec7bb
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/CompiledRouteTest.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\CompiledRoute;
+
+class CompiledRouteTest extends TestCase
+{
+ public function testAccessors()
+ {
+ $compiled = new CompiledRoute('prefix', 'regex', array('tokens'), array(), null, array(), array(), array('variables'));
+ $this->assertEquals('prefix', $compiled->getStaticPrefix(), '__construct() takes a static prefix as its second argument');
+ $this->assertEquals('regex', $compiled->getRegex(), '__construct() takes a regexp as its third argument');
+ $this->assertEquals(array('tokens'), $compiled->getTokens(), '__construct() takes an array of tokens as its fourth argument');
+ $this->assertEquals(array('variables'), $compiled->getVariables(), '__construct() takes an array of variables as its ninth argument');
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php b/assets/php/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php
new file mode 100644
index 0000000..97a34c9
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Loader\LoaderResolver;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
+
+class RoutingResolverPassTest extends TestCase
+{
+ public function testProcess()
+ {
+ $container = new ContainerBuilder();
+ $container->register('routing.resolver', LoaderResolver::class);
+ $container->register('loader1')->addTag('routing.loader');
+ $container->register('loader2')->addTag('routing.loader');
+
+ (new RoutingResolverPass())->process($container);
+
+ $this->assertEquals(
+ array(array('addLoader', array(new Reference('loader1'))), array('addLoader', array(new Reference('loader2')))),
+ $container->getDefinition('routing.resolver')->getMethodCalls()
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php
new file mode 100644
index 0000000..56bcab2
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses;
+
+abstract class AbstractClass
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php
new file mode 100644
index 0000000..a388277
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses;
+
+class BarClass
+{
+ public function routeAction($arg1, $arg2 = 'defaultValue2', $arg3 = 'defaultValue3')
+ {
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php
new file mode 100644
index 0000000..471968b
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses;
+
+class BazClass
+{
+ public function __invoke()
+ {
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php
new file mode 100644
index 0000000..320dc35
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses;
+
+class FooClass
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php
new file mode 100644
index 0000000..ee8f4b0
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php
@@ -0,0 +1,13 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures;
+
+use Symfony\Component\Routing\CompiledRoute;
+
+class CustomCompiledRoute extends CompiledRoute
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php
new file mode 100644
index 0000000..c2e2afd
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCompiler;
+
+class CustomRouteCompiler extends RouteCompiler
+{
+ /**
+ * {@inheritdoc}
+ */
+ public static function compile(Route $route)
+ {
+ return new CustomCompiledRoute('', '', array(), array());
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php
new file mode 100644
index 0000000..9fd5754
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures;
+
+use Symfony\Component\Routing\Loader\XmlFileLoader;
+use Symfony\Component\Config\Util\XmlUtils;
+
+/**
+ * XmlFileLoader with schema validation turned off.
+ */
+class CustomXmlFileLoader extends XmlFileLoader
+{
+ protected function loadFile($file)
+ {
+ return XmlUtils::loadFile($file, function () { return true; });
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php
new file mode 100644
index 0000000..de87895
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures\OtherAnnotatedClasses;
+
+trait AnonymousClassInTrait
+{
+ public function test()
+ {
+ return new class() {
+ public function foo()
+ {
+ }
+ };
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php
new file mode 100644
index 0000000..8900d34
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php
@@ -0,0 +1,3 @@
+class NoStartTagClass
+{
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php
new file mode 100644
index 0000000..729c9b4
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures\OtherAnnotatedClasses;
+
+class VariadicClass
+{
+ public function routeAction(...$params)
+ {
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php
new file mode 100644
index 0000000..15937bc
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Fixtures;
+
+use Symfony\Component\Routing\Matcher\UrlMatcher;
+use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface
+{
+ public function redirect($path, $route, $scheme = null)
+ {
+ return array(
+ '_controller' => 'Some controller reference...',
+ 'path' => $path,
+ 'scheme' => $scheme,
+ );
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/annotated.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/annotated.php
new file mode 100644
index 0000000..e69de29
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/bad_format.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/bad_format.yml
new file mode 100644
index 0000000..8ba50e2
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/bad_format.yml
@@ -0,0 +1,3 @@
+blog_show:
+ path: /blog/{slug}
+ defaults: { _controller: "MyBundle:Blog:show" }
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/bar.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/bar.xml
new file mode 100644
index 0000000..e69de29
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.xml
new file mode 100644
index 0000000..bbe727d
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ FrameworkBundle:Template:template
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.yml
new file mode 100644
index 0000000..4240b74
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.yml
@@ -0,0 +1,4 @@
+_static:
+ resource: routing.yml
+ defaults:
+ _controller: FrameworkBundle:Template:template
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.xml
new file mode 100644
index 0000000..378b9ca
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.yml
new file mode 100644
index 0000000..1a71c62
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.yml
@@ -0,0 +1,3 @@
+_static:
+ resource: routing.yml
+ controller: FrameworkBundle:Template:template
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.xml
new file mode 100644
index 0000000..e3c755a
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ AppBundle:Blog:index
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.yml
new file mode 100644
index 0000000..db1ab3c
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.yml
@@ -0,0 +1,5 @@
+_static:
+ resource: routing.yml
+ controller: FrameworkBundle:Template:template
+ defaults:
+ _controller: AppBundle:Homepage:show
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.xml
new file mode 100644
index 0000000..f47c57b
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ AppBundle:Blog:index
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.yml
new file mode 100644
index 0000000..00a2c0e
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.yml
@@ -0,0 +1,5 @@
+app_blog:
+ path: /blog
+ controller: AppBundle:Homepage:show
+ defaults:
+ _controller: AppBundle:Blog:index
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.xml
new file mode 100644
index 0000000..6420138
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ AppBundle:Blog:list
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.yml
new file mode 100644
index 0000000..cb71ec3
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/controller/routing.yml
@@ -0,0 +1,11 @@
+app_homepage:
+ path: /
+ controller: AppBundle:Homepage:show
+
+app_blog:
+ path: /blog
+ defaults:
+ _controller: AppBundle:Blog:list
+
+app_logout:
+ path: /logout
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml
new file mode 100644
index 0000000..d078836
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml
@@ -0,0 +1,2 @@
+route1:
+ path: /route/1
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml
new file mode 100644
index 0000000..938fb24
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml
@@ -0,0 +1,2 @@
+route2:
+ path: /route/2
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml
new file mode 100644
index 0000000..088cfb4
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml
@@ -0,0 +1,2 @@
+route3:
+ path: /route/3
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml
new file mode 100644
index 0000000..af829e5
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml
@@ -0,0 +1,3 @@
+_directory:
+ resource: "../directory"
+ type: directory
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php
new file mode 100644
index 0000000..9e9b910
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php
@@ -0,0 +1,37 @@
+context = $context;
+ }
+
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $canonicalMethod = $context->getMethod();
+
+ if ('HEAD' === $requestMethod) {
+ $canonicalMethod = 'GET';
+ }
+
+ if ('/' === $pathinfo && !$allow) {
+ throw new Symfony\Component\Routing\Exception\NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php
new file mode 100644
index 0000000..23a93c1
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php
@@ -0,0 +1,318 @@
+context = $context;
+ }
+
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $canonicalMethod = $context->getMethod();
+
+ if ('HEAD' === $requestMethod) {
+ $canonicalMethod = 'GET';
+ }
+
+ if (0 === strpos($pathinfo, '/foo')) {
+ // foo
+ if (preg_match('#^/foo/(?Pbaz|symfony)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',));
+ }
+
+ // foofoo
+ if ('/foofoo' === $pathinfo) {
+ return array ( 'def' => 'test', '_route' => 'foofoo',);
+ }
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/bar')) {
+ // bar
+ if (preg_match('#^/bar/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ());
+ if (!in_array($canonicalMethod, array('GET', 'HEAD'))) {
+ $allow = array_merge($allow, array('GET', 'HEAD'));
+ goto not_bar;
+ }
+
+ return $ret;
+ }
+ not_bar:
+
+ // barhead
+ if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ());
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_barhead;
+ }
+
+ return $ret;
+ }
+ not_barhead:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/test')) {
+ if (0 === strpos($pathinfo, '/test/baz')) {
+ // baz
+ if ('/test/baz' === $pathinfo) {
+ return array('_route' => 'baz');
+ }
+
+ // baz2
+ if ('/test/baz.html' === $pathinfo) {
+ return array('_route' => 'baz2');
+ }
+
+ // baz3
+ if ('/test/baz3/' === $pathinfo) {
+ return array('_route' => 'baz3');
+ }
+
+ }
+
+ // baz4
+ if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ());
+ }
+
+ // baz5
+ if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ());
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_baz5;
+ }
+
+ return $ret;
+ }
+ not_baz5:
+
+ // baz.baz6
+ if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ());
+ if (!in_array($requestMethod, array('PUT'))) {
+ $allow = array_merge($allow, array('PUT'));
+ goto not_bazbaz6;
+ }
+
+ return $ret;
+ }
+ not_bazbaz6:
+
+ }
+
+ // quoter
+ if (preg_match('#^/(?P[\']+)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ());
+ }
+
+ // space
+ if ('/spa ce' === $pathinfo) {
+ return array('_route' => 'space');
+ }
+
+ if (0 === strpos($pathinfo, '/a')) {
+ if (0 === strpos($pathinfo, '/a/b\'b')) {
+ // foo1
+ if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ());
+ }
+
+ // bar1
+ if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ());
+ }
+
+ }
+
+ // overridden
+ if (preg_match('#^/a/(?P.*)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ());
+ }
+
+ if (0 === strpos($pathinfo, '/a/b\'b')) {
+ // foo2
+ if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ());
+ }
+
+ // bar2
+ if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ());
+ }
+
+ }
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/multi')) {
+ // helloWorld
+ if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',));
+ }
+
+ // hey
+ if ('/multi/hey/' === $pathinfo) {
+ return array('_route' => 'hey');
+ }
+
+ // overridden2
+ if ('/multi/new' === $pathinfo) {
+ return array('_route' => 'overridden2');
+ }
+
+ }
+
+ // foo3
+ if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ());
+ }
+
+ // bar3
+ if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ());
+ }
+
+ if (0 === strpos($pathinfo, '/aba')) {
+ // ababa
+ if ('/ababa' === $pathinfo) {
+ return array('_route' => 'ababa');
+ }
+
+ // foo4
+ if (preg_match('#^/aba/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ());
+ }
+
+ }
+
+ $host = $context->getHost();
+
+ if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route1
+ if ('/route1' === $pathinfo) {
+ return array('_route' => 'route1');
+ }
+
+ // route2
+ if ('/c2/route2' === $pathinfo) {
+ return array('_route' => 'route2');
+ }
+
+ }
+
+ if (preg_match('#^b\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route3
+ if ('/c2/route3' === $pathinfo) {
+ return array('_route' => 'route3');
+ }
+
+ }
+
+ if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route4
+ if ('/route4' === $pathinfo) {
+ return array('_route' => 'route4');
+ }
+
+ }
+
+ if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route5
+ if ('/route5' === $pathinfo) {
+ return array('_route' => 'route5');
+ }
+
+ }
+
+ // route6
+ if ('/route6' === $pathinfo) {
+ return array('_route' => 'route6');
+ }
+
+ if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#sDi', $host, $hostMatches)) {
+ if (0 === strpos($pathinfo, '/route1')) {
+ // route11
+ if ('/route11' === $pathinfo) {
+ return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ());
+ }
+
+ // route12
+ if ('/route12' === $pathinfo) {
+ return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',));
+ }
+
+ // route13
+ if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ());
+ }
+
+ // route14
+ if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',));
+ }
+
+ }
+
+ }
+
+ if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route15
+ if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
+ }
+
+ }
+
+ // route16
+ if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',));
+ }
+
+ // route17
+ if ('/route17' === $pathinfo) {
+ return array('_route' => 'route17');
+ }
+
+ // a
+ if ('/a/a...' === $pathinfo) {
+ return array('_route' => 'a');
+ }
+
+ if (0 === strpos($pathinfo, '/a/b')) {
+ // b
+ if (preg_match('#^/a/b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ());
+ }
+
+ // c
+ if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ());
+ }
+
+ }
+
+ if ('/' === $pathinfo && !$allow) {
+ throw new Symfony\Component\Routing\Exception\NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php
new file mode 100644
index 0000000..e430adb
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php
@@ -0,0 +1,380 @@
+context = $context;
+ }
+
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $canonicalMethod = $context->getMethod();
+
+ if ('HEAD' === $requestMethod) {
+ $canonicalMethod = 'GET';
+ }
+
+ if (0 === strpos($pathinfo, '/foo')) {
+ // foo
+ if (preg_match('#^/foo/(?Pbaz|symfony)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',));
+ }
+
+ // foofoo
+ if ('/foofoo' === $pathinfo) {
+ return array ( 'def' => 'test', '_route' => 'foofoo',);
+ }
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/bar')) {
+ // bar
+ if (preg_match('#^/bar/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ());
+ if (!in_array($canonicalMethod, array('GET', 'HEAD'))) {
+ $allow = array_merge($allow, array('GET', 'HEAD'));
+ goto not_bar;
+ }
+
+ return $ret;
+ }
+ not_bar:
+
+ // barhead
+ if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ());
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_barhead;
+ }
+
+ return $ret;
+ }
+ not_barhead:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/test')) {
+ if (0 === strpos($pathinfo, '/test/baz')) {
+ // baz
+ if ('/test/baz' === $pathinfo) {
+ return array('_route' => 'baz');
+ }
+
+ // baz2
+ if ('/test/baz.html' === $pathinfo) {
+ return array('_route' => 'baz2');
+ }
+
+ // baz3
+ if ('/test/baz3' === $trimmedPathinfo) {
+ $ret = array('_route' => 'baz3');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_baz3;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz3'));
+ }
+
+ return $ret;
+ }
+ not_baz3:
+
+ }
+
+ // baz4
+ if (preg_match('#^/test/(?P[^/]++)/?$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ());
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_baz4;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz4'));
+ }
+
+ return $ret;
+ }
+ not_baz4:
+
+ // baz5
+ if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ());
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_baz5;
+ }
+
+ return $ret;
+ }
+ not_baz5:
+
+ // baz.baz6
+ if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ());
+ if (!in_array($requestMethod, array('PUT'))) {
+ $allow = array_merge($allow, array('PUT'));
+ goto not_bazbaz6;
+ }
+
+ return $ret;
+ }
+ not_bazbaz6:
+
+ }
+
+ // quoter
+ if (preg_match('#^/(?P[\']+)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ());
+ }
+
+ // space
+ if ('/spa ce' === $pathinfo) {
+ return array('_route' => 'space');
+ }
+
+ if (0 === strpos($pathinfo, '/a')) {
+ if (0 === strpos($pathinfo, '/a/b\'b')) {
+ // foo1
+ if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ());
+ }
+
+ // bar1
+ if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ());
+ }
+
+ }
+
+ // overridden
+ if (preg_match('#^/a/(?P.*)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ());
+ }
+
+ if (0 === strpos($pathinfo, '/a/b\'b')) {
+ // foo2
+ if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ());
+ }
+
+ // bar2
+ if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ());
+ }
+
+ }
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/multi')) {
+ // helloWorld
+ if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',));
+ }
+
+ // hey
+ if ('/multi/hey' === $trimmedPathinfo) {
+ $ret = array('_route' => 'hey');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_hey;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'hey'));
+ }
+
+ return $ret;
+ }
+ not_hey:
+
+ // overridden2
+ if ('/multi/new' === $pathinfo) {
+ return array('_route' => 'overridden2');
+ }
+
+ }
+
+ // foo3
+ if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ());
+ }
+
+ // bar3
+ if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ());
+ }
+
+ if (0 === strpos($pathinfo, '/aba')) {
+ // ababa
+ if ('/ababa' === $pathinfo) {
+ return array('_route' => 'ababa');
+ }
+
+ // foo4
+ if (preg_match('#^/aba/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ());
+ }
+
+ }
+
+ $host = $context->getHost();
+
+ if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route1
+ if ('/route1' === $pathinfo) {
+ return array('_route' => 'route1');
+ }
+
+ // route2
+ if ('/c2/route2' === $pathinfo) {
+ return array('_route' => 'route2');
+ }
+
+ }
+
+ if (preg_match('#^b\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route3
+ if ('/c2/route3' === $pathinfo) {
+ return array('_route' => 'route3');
+ }
+
+ }
+
+ if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route4
+ if ('/route4' === $pathinfo) {
+ return array('_route' => 'route4');
+ }
+
+ }
+
+ if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route5
+ if ('/route5' === $pathinfo) {
+ return array('_route' => 'route5');
+ }
+
+ }
+
+ // route6
+ if ('/route6' === $pathinfo) {
+ return array('_route' => 'route6');
+ }
+
+ if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#sDi', $host, $hostMatches)) {
+ if (0 === strpos($pathinfo, '/route1')) {
+ // route11
+ if ('/route11' === $pathinfo) {
+ return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ());
+ }
+
+ // route12
+ if ('/route12' === $pathinfo) {
+ return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',));
+ }
+
+ // route13
+ if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ());
+ }
+
+ // route14
+ if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',));
+ }
+
+ }
+
+ }
+
+ if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) {
+ // route15
+ if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
+ }
+
+ }
+
+ // route16
+ if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',));
+ }
+
+ // route17
+ if ('/route17' === $pathinfo) {
+ return array('_route' => 'route17');
+ }
+
+ // a
+ if ('/a/a...' === $pathinfo) {
+ return array('_route' => 'a');
+ }
+
+ if (0 === strpos($pathinfo, '/a/b')) {
+ // b
+ if (preg_match('#^/a/b/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ());
+ }
+
+ // c
+ if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ());
+ }
+
+ }
+
+ // secure
+ if ('/secure' === $pathinfo) {
+ $ret = array('_route' => 'secure');
+ $requiredSchemes = array ( 'https' => 0,);
+ if (!isset($requiredSchemes[$context->getScheme()])) {
+ if ('GET' !== $canonicalMethod) {
+ goto not_secure;
+ }
+
+ return array_replace($ret, $this->redirect($rawPathinfo, 'secure', key($requiredSchemes)));
+ }
+
+ return $ret;
+ }
+ not_secure:
+
+ // nonsecure
+ if ('/nonsecure' === $pathinfo) {
+ $ret = array('_route' => 'nonsecure');
+ $requiredSchemes = array ( 'http' => 0,);
+ if (!isset($requiredSchemes[$context->getScheme()])) {
+ if ('GET' !== $canonicalMethod) {
+ goto not_nonsecure;
+ }
+
+ return array_replace($ret, $this->redirect($rawPathinfo, 'nonsecure', key($requiredSchemes)));
+ }
+
+ return $ret;
+ }
+ not_nonsecure:
+
+ if ('/' === $pathinfo && !$allow) {
+ throw new Symfony\Component\Routing\Exception\NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php
new file mode 100644
index 0000000..67c4667
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php
@@ -0,0 +1,55 @@
+context = $context;
+ }
+
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $canonicalMethod = $context->getMethod();
+
+ if ('HEAD' === $requestMethod) {
+ $canonicalMethod = 'GET';
+ }
+
+ if (0 === strpos($pathinfo, '/rootprefix')) {
+ // static
+ if ('/rootprefix/test' === $pathinfo) {
+ return array('_route' => 'static');
+ }
+
+ // dynamic
+ if (preg_match('#^/rootprefix/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ());
+ }
+
+ }
+
+ // with-condition
+ if ('/with-condition' === $pathinfo && ($context->getMethod() == "GET")) {
+ return array('_route' => 'with-condition');
+ }
+
+ if ('/' === $pathinfo && !$allow) {
+ throw new Symfony\Component\Routing\Exception\NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php
new file mode 100644
index 0000000..ed07194
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php
@@ -0,0 +1,112 @@
+context = $context;
+ }
+
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $canonicalMethod = $context->getMethod();
+
+ if ('HEAD' === $requestMethod) {
+ $canonicalMethod = 'GET';
+ }
+
+ // just_head
+ if ('/just_head' === $pathinfo) {
+ $ret = array('_route' => 'just_head');
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_just_head;
+ }
+
+ return $ret;
+ }
+ not_just_head:
+
+ // head_and_get
+ if ('/head_and_get' === $pathinfo) {
+ $ret = array('_route' => 'head_and_get');
+ if (!in_array($canonicalMethod, array('HEAD', 'GET'))) {
+ $allow = array_merge($allow, array('HEAD', 'GET'));
+ goto not_head_and_get;
+ }
+
+ return $ret;
+ }
+ not_head_and_get:
+
+ // get_and_head
+ if ('/get_and_head' === $pathinfo) {
+ $ret = array('_route' => 'get_and_head');
+ if (!in_array($canonicalMethod, array('GET', 'HEAD'))) {
+ $allow = array_merge($allow, array('GET', 'HEAD'));
+ goto not_get_and_head;
+ }
+
+ return $ret;
+ }
+ not_get_and_head:
+
+ // post_and_head
+ if ('/post_and_head' === $pathinfo) {
+ $ret = array('_route' => 'post_and_head');
+ if (!in_array($requestMethod, array('POST', 'HEAD'))) {
+ $allow = array_merge($allow, array('POST', 'HEAD'));
+ goto not_post_and_head;
+ }
+
+ return $ret;
+ }
+ not_post_and_head:
+
+ if (0 === strpos($pathinfo, '/put_and_post')) {
+ // put_and_post
+ if ('/put_and_post' === $pathinfo) {
+ $ret = array('_route' => 'put_and_post');
+ if (!in_array($requestMethod, array('PUT', 'POST'))) {
+ $allow = array_merge($allow, array('PUT', 'POST'));
+ goto not_put_and_post;
+ }
+
+ return $ret;
+ }
+ not_put_and_post:
+
+ // put_and_get_and_head
+ if ('/put_and_post' === $pathinfo) {
+ $ret = array('_route' => 'put_and_get_and_head');
+ if (!in_array($canonicalMethod, array('PUT', 'GET', 'HEAD'))) {
+ $allow = array_merge($allow, array('PUT', 'GET', 'HEAD'));
+ goto not_put_and_get_and_head;
+ }
+
+ return $ret;
+ }
+ not_put_and_get_and_head:
+
+ }
+
+ if ('/' === $pathinfo && !$allow) {
+ throw new Symfony\Component\Routing\Exception\NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php
new file mode 100644
index 0000000..2b22513
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php
@@ -0,0 +1,209 @@
+context = $context;
+ }
+
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $canonicalMethod = $context->getMethod();
+
+ if ('HEAD' === $requestMethod) {
+ $canonicalMethod = 'GET';
+ }
+
+ if (0 === strpos($pathinfo, '/a')) {
+ // a_first
+ if ('/a/11' === $pathinfo) {
+ return array('_route' => 'a_first');
+ }
+
+ // a_second
+ if ('/a/22' === $pathinfo) {
+ return array('_route' => 'a_second');
+ }
+
+ // a_third
+ if ('/a/333' === $pathinfo) {
+ return array('_route' => 'a_third');
+ }
+
+ }
+
+ // a_wildcard
+ if (preg_match('#^/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'a_wildcard')), array ());
+ }
+
+ if (0 === strpos($pathinfo, '/a')) {
+ // a_fourth
+ if ('/a/44' === $trimmedPathinfo) {
+ $ret = array('_route' => 'a_fourth');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_a_fourth;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fourth'));
+ }
+
+ return $ret;
+ }
+ not_a_fourth:
+
+ // a_fifth
+ if ('/a/55' === $trimmedPathinfo) {
+ $ret = array('_route' => 'a_fifth');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_a_fifth;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fifth'));
+ }
+
+ return $ret;
+ }
+ not_a_fifth:
+
+ // a_sixth
+ if ('/a/66' === $trimmedPathinfo) {
+ $ret = array('_route' => 'a_sixth');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_a_sixth;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_sixth'));
+ }
+
+ return $ret;
+ }
+ not_a_sixth:
+
+ }
+
+ // nested_wildcard
+ if (0 === strpos($pathinfo, '/nested') && preg_match('#^/nested/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'nested_wildcard')), array ());
+ }
+
+ if (0 === strpos($pathinfo, '/nested/group')) {
+ // nested_a
+ if ('/nested/group/a' === $trimmedPathinfo) {
+ $ret = array('_route' => 'nested_a');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_nested_a;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_a'));
+ }
+
+ return $ret;
+ }
+ not_nested_a:
+
+ // nested_b
+ if ('/nested/group/b' === $trimmedPathinfo) {
+ $ret = array('_route' => 'nested_b');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_nested_b;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_b'));
+ }
+
+ return $ret;
+ }
+ not_nested_b:
+
+ // nested_c
+ if ('/nested/group/c' === $trimmedPathinfo) {
+ $ret = array('_route' => 'nested_c');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_nested_c;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_c'));
+ }
+
+ return $ret;
+ }
+ not_nested_c:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/slashed/group')) {
+ // slashed_a
+ if ('/slashed/group' === $trimmedPathinfo) {
+ $ret = array('_route' => 'slashed_a');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_slashed_a;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_a'));
+ }
+
+ return $ret;
+ }
+ not_slashed_a:
+
+ // slashed_b
+ if ('/slashed/group/b' === $trimmedPathinfo) {
+ $ret = array('_route' => 'slashed_b');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_slashed_b;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_b'));
+ }
+
+ return $ret;
+ }
+ not_slashed_b:
+
+ // slashed_c
+ if ('/slashed/group/c' === $trimmedPathinfo) {
+ $ret = array('_route' => 'slashed_c');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_slashed_c;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_c'));
+ }
+
+ return $ret;
+ }
+ not_slashed_c:
+
+ }
+
+ if ('/' === $pathinfo && !$allow) {
+ throw new Symfony\Component\Routing\Exception\NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php
new file mode 100644
index 0000000..48ecdf8
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php
@@ -0,0 +1,213 @@
+context = $context;
+ }
+
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $canonicalMethod = $context->getMethod();
+
+ if ('HEAD' === $requestMethod) {
+ $canonicalMethod = 'GET';
+ }
+
+ if (0 === strpos($pathinfo, '/trailing/simple')) {
+ // simple_trailing_slash_no_methods
+ if ('/trailing/simple/no-methods/' === $pathinfo) {
+ return array('_route' => 'simple_trailing_slash_no_methods');
+ }
+
+ // simple_trailing_slash_GET_method
+ if ('/trailing/simple/get-method/' === $pathinfo) {
+ $ret = array('_route' => 'simple_trailing_slash_GET_method');
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_simple_trailing_slash_GET_method;
+ }
+
+ return $ret;
+ }
+ not_simple_trailing_slash_GET_method:
+
+ // simple_trailing_slash_HEAD_method
+ if ('/trailing/simple/head-method/' === $pathinfo) {
+ $ret = array('_route' => 'simple_trailing_slash_HEAD_method');
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_simple_trailing_slash_HEAD_method;
+ }
+
+ return $ret;
+ }
+ not_simple_trailing_slash_HEAD_method:
+
+ // simple_trailing_slash_POST_method
+ if ('/trailing/simple/post-method/' === $pathinfo) {
+ $ret = array('_route' => 'simple_trailing_slash_POST_method');
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_simple_trailing_slash_POST_method;
+ }
+
+ return $ret;
+ }
+ not_simple_trailing_slash_POST_method:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/trailing/regex')) {
+ // regex_trailing_slash_no_methods
+ if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ());
+ }
+
+ // regex_trailing_slash_GET_method
+ if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ());
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_regex_trailing_slash_GET_method;
+ }
+
+ return $ret;
+ }
+ not_regex_trailing_slash_GET_method:
+
+ // regex_trailing_slash_HEAD_method
+ if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ());
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_regex_trailing_slash_HEAD_method;
+ }
+
+ return $ret;
+ }
+ not_regex_trailing_slash_HEAD_method:
+
+ // regex_trailing_slash_POST_method
+ if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ());
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_regex_trailing_slash_POST_method;
+ }
+
+ return $ret;
+ }
+ not_regex_trailing_slash_POST_method:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/not-trailing/simple')) {
+ // simple_not_trailing_slash_no_methods
+ if ('/not-trailing/simple/no-methods' === $pathinfo) {
+ return array('_route' => 'simple_not_trailing_slash_no_methods');
+ }
+
+ // simple_not_trailing_slash_GET_method
+ if ('/not-trailing/simple/get-method' === $pathinfo) {
+ $ret = array('_route' => 'simple_not_trailing_slash_GET_method');
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_simple_not_trailing_slash_GET_method;
+ }
+
+ return $ret;
+ }
+ not_simple_not_trailing_slash_GET_method:
+
+ // simple_not_trailing_slash_HEAD_method
+ if ('/not-trailing/simple/head-method' === $pathinfo) {
+ $ret = array('_route' => 'simple_not_trailing_slash_HEAD_method');
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_simple_not_trailing_slash_HEAD_method;
+ }
+
+ return $ret;
+ }
+ not_simple_not_trailing_slash_HEAD_method:
+
+ // simple_not_trailing_slash_POST_method
+ if ('/not-trailing/simple/post-method' === $pathinfo) {
+ $ret = array('_route' => 'simple_not_trailing_slash_POST_method');
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_simple_not_trailing_slash_POST_method;
+ }
+
+ return $ret;
+ }
+ not_simple_not_trailing_slash_POST_method:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/not-trailing/regex')) {
+ // regex_not_trailing_slash_no_methods
+ if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ());
+ }
+
+ // regex_not_trailing_slash_GET_method
+ if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ());
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_regex_not_trailing_slash_GET_method;
+ }
+
+ return $ret;
+ }
+ not_regex_not_trailing_slash_GET_method:
+
+ // regex_not_trailing_slash_HEAD_method
+ if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ());
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_regex_not_trailing_slash_HEAD_method;
+ }
+
+ return $ret;
+ }
+ not_regex_not_trailing_slash_HEAD_method:
+
+ // regex_not_trailing_slash_POST_method
+ if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ());
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_regex_not_trailing_slash_POST_method;
+ }
+
+ return $ret;
+ }
+ not_regex_not_trailing_slash_POST_method:
+
+ }
+
+ if ('/' === $pathinfo && !$allow) {
+ throw new Symfony\Component\Routing\Exception\NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php
new file mode 100644
index 0000000..81d76ea
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php
@@ -0,0 +1,249 @@
+context = $context;
+ }
+
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $canonicalMethod = $context->getMethod();
+
+ if ('HEAD' === $requestMethod) {
+ $canonicalMethod = 'GET';
+ }
+
+ if (0 === strpos($pathinfo, '/trailing/simple')) {
+ // simple_trailing_slash_no_methods
+ if ('/trailing/simple/no-methods' === $trimmedPathinfo) {
+ $ret = array('_route' => 'simple_trailing_slash_no_methods');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_simple_trailing_slash_no_methods;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_no_methods'));
+ }
+
+ return $ret;
+ }
+ not_simple_trailing_slash_no_methods:
+
+ // simple_trailing_slash_GET_method
+ if ('/trailing/simple/get-method' === $trimmedPathinfo) {
+ $ret = array('_route' => 'simple_trailing_slash_GET_method');
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_simple_trailing_slash_GET_method;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_GET_method'));
+ }
+
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_simple_trailing_slash_GET_method;
+ }
+
+ return $ret;
+ }
+ not_simple_trailing_slash_GET_method:
+
+ // simple_trailing_slash_HEAD_method
+ if ('/trailing/simple/head-method/' === $pathinfo) {
+ $ret = array('_route' => 'simple_trailing_slash_HEAD_method');
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_simple_trailing_slash_HEAD_method;
+ }
+
+ return $ret;
+ }
+ not_simple_trailing_slash_HEAD_method:
+
+ // simple_trailing_slash_POST_method
+ if ('/trailing/simple/post-method/' === $pathinfo) {
+ $ret = array('_route' => 'simple_trailing_slash_POST_method');
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_simple_trailing_slash_POST_method;
+ }
+
+ return $ret;
+ }
+ not_simple_trailing_slash_POST_method:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/trailing/regex')) {
+ // regex_trailing_slash_no_methods
+ if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/?$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ());
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_regex_trailing_slash_no_methods;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_no_methods'));
+ }
+
+ return $ret;
+ }
+ not_regex_trailing_slash_no_methods:
+
+ // regex_trailing_slash_GET_method
+ if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/?$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ());
+ if ('/' === substr($pathinfo, -1)) {
+ // no-op
+ } elseif ('GET' !== $canonicalMethod) {
+ goto not_regex_trailing_slash_GET_method;
+ } else {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_GET_method'));
+ }
+
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_regex_trailing_slash_GET_method;
+ }
+
+ return $ret;
+ }
+ not_regex_trailing_slash_GET_method:
+
+ // regex_trailing_slash_HEAD_method
+ if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ());
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_regex_trailing_slash_HEAD_method;
+ }
+
+ return $ret;
+ }
+ not_regex_trailing_slash_HEAD_method:
+
+ // regex_trailing_slash_POST_method
+ if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ());
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_regex_trailing_slash_POST_method;
+ }
+
+ return $ret;
+ }
+ not_regex_trailing_slash_POST_method:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/not-trailing/simple')) {
+ // simple_not_trailing_slash_no_methods
+ if ('/not-trailing/simple/no-methods' === $pathinfo) {
+ return array('_route' => 'simple_not_trailing_slash_no_methods');
+ }
+
+ // simple_not_trailing_slash_GET_method
+ if ('/not-trailing/simple/get-method' === $pathinfo) {
+ $ret = array('_route' => 'simple_not_trailing_slash_GET_method');
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_simple_not_trailing_slash_GET_method;
+ }
+
+ return $ret;
+ }
+ not_simple_not_trailing_slash_GET_method:
+
+ // simple_not_trailing_slash_HEAD_method
+ if ('/not-trailing/simple/head-method' === $pathinfo) {
+ $ret = array('_route' => 'simple_not_trailing_slash_HEAD_method');
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_simple_not_trailing_slash_HEAD_method;
+ }
+
+ return $ret;
+ }
+ not_simple_not_trailing_slash_HEAD_method:
+
+ // simple_not_trailing_slash_POST_method
+ if ('/not-trailing/simple/post-method' === $pathinfo) {
+ $ret = array('_route' => 'simple_not_trailing_slash_POST_method');
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_simple_not_trailing_slash_POST_method;
+ }
+
+ return $ret;
+ }
+ not_simple_not_trailing_slash_POST_method:
+
+ }
+
+ elseif (0 === strpos($pathinfo, '/not-trailing/regex')) {
+ // regex_not_trailing_slash_no_methods
+ if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ());
+ }
+
+ // regex_not_trailing_slash_GET_method
+ if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ());
+ if (!in_array($canonicalMethod, array('GET'))) {
+ $allow = array_merge($allow, array('GET'));
+ goto not_regex_not_trailing_slash_GET_method;
+ }
+
+ return $ret;
+ }
+ not_regex_not_trailing_slash_GET_method:
+
+ // regex_not_trailing_slash_HEAD_method
+ if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ());
+ if (!in_array($requestMethod, array('HEAD'))) {
+ $allow = array_merge($allow, array('HEAD'));
+ goto not_regex_not_trailing_slash_HEAD_method;
+ }
+
+ return $ret;
+ }
+ not_regex_not_trailing_slash_HEAD_method:
+
+ // regex_not_trailing_slash_POST_method
+ if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) {
+ $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ());
+ if (!in_array($requestMethod, array('POST'))) {
+ $allow = array_merge($allow, array('POST'));
+ goto not_regex_not_trailing_slash_POST_method;
+ }
+
+ return $ret;
+ }
+ not_regex_not_trailing_slash_POST_method:
+
+ }
+
+ if ('/' === $pathinfo && !$allow) {
+ throw new Symfony\Component\Routing\Exception\NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/empty.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/empty.yml
new file mode 100644
index 0000000..e69de29
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/file_resource.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/file_resource.yml
new file mode 100644
index 0000000..e69de29
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/foo.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/foo.xml
new file mode 100644
index 0000000..e69de29
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/foo1.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/foo1.xml
new file mode 100644
index 0000000..e69de29
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.xml
new file mode 100644
index 0000000..0d31eeb
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.yml
new file mode 100644
index 0000000..ba3bc22
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/bar.yml
@@ -0,0 +1,4 @@
+bar_route:
+ path: /bar
+ defaults:
+ _controller: AppBundle:Bar:view
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.xml
new file mode 100644
index 0000000..3abba1a
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.yml
new file mode 100644
index 0000000..f7d8c67
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/baz.yml
@@ -0,0 +1,4 @@
+baz_route:
+ path: /baz
+ defaults:
+ _controller: AppBundle:Baz:view
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.xml
new file mode 100644
index 0000000..ca6b1b5
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.yml
new file mode 100644
index 0000000..d1ae585
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.yml
@@ -0,0 +1,2 @@
+_static:
+ resource: ba?.yml
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.xml
new file mode 100644
index 0000000..15f5698
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml
new file mode 100644
index 0000000..f56ddbd
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml
@@ -0,0 +1,2 @@
+_static:
+ resource: b?r.yml
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php
new file mode 100644
index 0000000..897fa11
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php
@@ -0,0 +1,7 @@
+import('php_dsl_ba?.php');
+};
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php
new file mode 100644
index 0000000..e2b91b1
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php
@@ -0,0 +1,12 @@
+collection();
+
+ $collection->add('bar_route', '/bar')
+ ->defaults(array('_controller' => 'AppBundle:Bar:view'));
+
+ return $collection;
+};
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php
new file mode 100644
index 0000000..ca8f188
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php
@@ -0,0 +1,12 @@
+collection();
+
+ $collection->add('baz_route', '/baz')
+ ->defaults(array('_controller' => 'AppBundle:Baz:view'));
+
+ return $collection;
+};
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/incomplete.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/incomplete.yml
new file mode 100644
index 0000000..df64d32
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/incomplete.yml
@@ -0,0 +1,2 @@
+blog_show:
+ defaults: { _controller: MyBlogBundle:Blog:show }
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml
new file mode 100644
index 0000000..f93bf9c
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+ true
+ 1
+ 3.5
+ foo
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml
new file mode 100644
index 0000000..987086d
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+
+ true
+ 1
+ 3.5
+ foo
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml
new file mode 100644
index 0000000..32d393c
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml
new file mode 100644
index 0000000..c70e03c
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml
new file mode 100644
index 0000000..47feb29
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml
new file mode 100644
index 0000000..6d77065
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml
new file mode 100644
index 0000000..2beee61
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml
new file mode 100644
index 0000000..8fd8954
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_id.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_id.xml
new file mode 100644
index 0000000..4ea4115
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_id.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_path.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_path.xml
new file mode 100644
index 0000000..ef5bc08
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/missing_path.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml
new file mode 100644
index 0000000..e33955a
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ MyBundle:Blog:show
+ \w+
+ en|fr|de
+ RouteCompiler
+
+ 1
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml
new file mode 100644
index 0000000..a3e9473
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml
@@ -0,0 +1,3 @@
+blog_show:
+ resource: validpattern.yml
+ path: /test
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml
new file mode 100644
index 0000000..547cda3
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml
@@ -0,0 +1,3 @@
+blog_show:
+ path: /blog/{slug}
+ type: custom
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml
new file mode 100644
index 0000000..dc147d2
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ MyBundle:Blog:show
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml
@@ -0,0 +1 @@
+foo
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml
new file mode 100644
index 0000000..cfa9992
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml
@@ -0,0 +1 @@
+route: string
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml
new file mode 100644
index 0000000..015e270
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml
@@ -0,0 +1,3 @@
+someroute:
+ resource: path/to/some.yml
+ name_prefix: test_
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml
new file mode 100644
index 0000000..863ef03
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ bar
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml
new file mode 100644
index 0000000..908958c
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ MyBundle:Blog:show
+
+ baz
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/null_values.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/null_values.xml
new file mode 100644
index 0000000..f9e2aa2
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/null_values.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ foo
+ bar
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl.php
new file mode 100644
index 0000000..0780c9f
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl.php
@@ -0,0 +1,22 @@
+collection()
+ ->add('foo', '/foo')
+ ->condition('abc')
+ ->options(array('utf8' => true))
+ ->add('buz', 'zub')
+ ->controller('foo:act');
+
+ $routes->import('php_dsl_sub.php')
+ ->prefix('/sub')
+ ->requirements(array('id' => '\d+'));
+
+ $routes->add('ouf', '/ouf')
+ ->schemes(array('https'))
+ ->methods(array('GET'))
+ ->defaults(array('id' => 0));
+};
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php
new file mode 100644
index 0000000..9eb444d
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php
@@ -0,0 +1,14 @@
+collection('c_')
+ ->prefix('pub');
+
+ $add('bar', '/bar');
+
+ $add->collection('pub_')
+ ->host('host')
+ ->add('buz', 'buz');
+};
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml
new file mode 100644
index 0000000..ecfde28
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+ AcmeBlogBundle:Blog:index
+
+
+
+ true
+
+
+ 1
+
+
+ 3.5
+
+
+ false
+
+
+ 1
+
+
+ 0
+
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml
new file mode 100644
index 0000000..78be239
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml
@@ -0,0 +1,2 @@
+"#$péß^a|":
+ path: "true"
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.php
new file mode 100644
index 0000000..edc16d8
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.php
@@ -0,0 +1,18 @@
+add('blog_show', new Route(
+ '/blog/{slug}',
+ array('_controller' => 'MyBlogBundle:Blog:show'),
+ array('locale' => '\w+'),
+ array('compiler_class' => 'RouteCompiler'),
+ '{locale}.example.com',
+ array('https'),
+ array('GET', 'POST', 'put', 'OpTiOnS'),
+ 'context.getMethod() == "GET"'
+));
+
+return $collection;
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.xml
new file mode 100644
index 0000000..dbc72e4
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ MyBundle:Blog:show
+ \w+
+
+ context.getMethod() == "GET"
+
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.yml
new file mode 100644
index 0000000..565abaa
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validpattern.yml
@@ -0,0 +1,13 @@
+blog_show:
+ path: /blog/{slug}
+ defaults: { _controller: "MyBundle:Blog:show" }
+ host: "{locale}.example.com"
+ requirements: { 'locale': '\w+' }
+ methods: ['GET','POST','put','OpTiOnS']
+ schemes: ['https']
+ condition: 'context.getMethod() == "GET"'
+ options:
+ compiler_class: RouteCompiler
+
+blog_show_inherited:
+ path: /blog/{slug}
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.php
new file mode 100644
index 0000000..482c80b
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.php
@@ -0,0 +1,18 @@
+import('validpattern.php');
+$collection->addDefaults(array(
+ 'foo' => 123,
+));
+$collection->addRequirements(array(
+ 'foo' => '\d+',
+));
+$collection->addOptions(array(
+ 'foo' => 'bar',
+));
+$collection->setCondition('context.getMethod() == "POST"');
+$collection->addPrefix('/prefix');
+
+return $collection;
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.xml b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.xml
new file mode 100644
index 0000000..b7a15dd
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ 123
+ \d+
+
+ context.getMethod() == "POST"
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.yml b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.yml
new file mode 100644
index 0000000..faf2263
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/validresource.yml
@@ -0,0 +1,8 @@
+_blog:
+ resource: validpattern.yml
+ prefix: /{foo}
+ defaults: { 'foo': '123' }
+ requirements: { 'foo': '\d+' }
+ options: { 'foo': 'bar' }
+ host: ""
+ condition: 'context.getMethod() == "POST"'
diff --git a/assets/php/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php b/assets/php/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php
new file mode 100644
index 0000000..5871420
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php
@@ -0,0 +1,5 @@
+
+
+
diff --git a/assets/php/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/assets/php/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php
new file mode 100644
index 0000000..f84802b
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php
@@ -0,0 +1,181 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Generator\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper;
+use Symfony\Component\Routing\RequestContext;
+
+class PhpGeneratorDumperTest extends TestCase
+{
+ /**
+ * @var RouteCollection
+ */
+ private $routeCollection;
+
+ /**
+ * @var PhpGeneratorDumper
+ */
+ private $generatorDumper;
+
+ /**
+ * @var string
+ */
+ private $testTmpFilepath;
+
+ /**
+ * @var string
+ */
+ private $largeTestTmpFilepath;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->routeCollection = new RouteCollection();
+ $this->generatorDumper = new PhpGeneratorDumper($this->routeCollection);
+ $this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php';
+ $this->largeTestTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.large.php';
+ @unlink($this->testTmpFilepath);
+ @unlink($this->largeTestTmpFilepath);
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ @unlink($this->testTmpFilepath);
+
+ $this->routeCollection = null;
+ $this->generatorDumper = null;
+ $this->testTmpFilepath = null;
+ }
+
+ public function testDumpWithRoutes()
+ {
+ $this->routeCollection->add('Test', new Route('/testing/{foo}'));
+ $this->routeCollection->add('Test2', new Route('/testing2'));
+
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
+ include $this->testTmpFilepath;
+
+ $projectUrlGenerator = new \ProjectUrlGenerator(new RequestContext('/app.php'));
+
+ $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
+ $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter);
+ $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter);
+ $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter);
+ $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
+ }
+
+ public function testDumpWithTooManyRoutes()
+ {
+ if (defined('HHVM_VERSION_ID')) {
+ $this->markTestSkipped('HHVM consumes too much memory on this test.');
+ }
+
+ $this->routeCollection->add('Test', new Route('/testing/{foo}'));
+ for ($i = 0; $i < 32769; ++$i) {
+ $this->routeCollection->add('route_'.$i, new Route('/route_'.$i));
+ }
+ $this->routeCollection->add('Test2', new Route('/testing2'));
+
+ file_put_contents($this->largeTestTmpFilepath, $this->generatorDumper->dump(array(
+ 'class' => 'ProjectLargeUrlGenerator',
+ )));
+ $this->routeCollection = $this->generatorDumper = null;
+ include $this->largeTestTmpFilepath;
+
+ $projectUrlGenerator = new \ProjectLargeUrlGenerator(new RequestContext('/app.php'));
+
+ $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
+ $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter);
+ $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter);
+ $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter);
+ $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testDumpWithoutRoutes()
+ {
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'WithoutRoutesUrlGenerator')));
+ include $this->testTmpFilepath;
+
+ $projectUrlGenerator = new \WithoutRoutesUrlGenerator(new RequestContext('/app.php'));
+
+ $projectUrlGenerator->generate('Test', array());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException
+ */
+ public function testGenerateNonExistingRoute()
+ {
+ $this->routeCollection->add('Test', new Route('/test'));
+
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'NonExistingRoutesUrlGenerator')));
+ include $this->testTmpFilepath;
+
+ $projectUrlGenerator = new \NonExistingRoutesUrlGenerator(new RequestContext());
+ $url = $projectUrlGenerator->generate('NonExisting', array());
+ }
+
+ public function testDumpForRouteWithDefaults()
+ {
+ $this->routeCollection->add('Test', new Route('/testing/{foo}', array('foo' => 'bar')));
+
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'DefaultRoutesUrlGenerator')));
+ include $this->testTmpFilepath;
+
+ $projectUrlGenerator = new \DefaultRoutesUrlGenerator(new RequestContext());
+ $url = $projectUrlGenerator->generate('Test', array());
+
+ $this->assertEquals('/testing', $url);
+ }
+
+ public function testDumpWithSchemeRequirement()
+ {
+ $this->routeCollection->add('Test1', new Route('/testing', array(), array(), array(), '', array('ftp', 'https')));
+
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'SchemeUrlGenerator')));
+ include $this->testTmpFilepath;
+
+ $projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php'));
+
+ $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('ftp://localhost/app.php/testing', $absoluteUrl);
+ $this->assertEquals('ftp://localhost/app.php/testing', $relativeUrl);
+
+ $projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php', 'GET', 'localhost', 'https'));
+
+ $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl);
+ $this->assertEquals('/app.php/testing', $relativeUrl);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php b/assets/php/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php
new file mode 100644
index 0000000..68add77
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php
@@ -0,0 +1,724 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Generator;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\Generator\UrlGenerator;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\RequestContext;
+
+class UrlGeneratorTest extends TestCase
+{
+ public function testAbsoluteUrlWithPort80()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+
+ $this->assertEquals('http://localhost/app.php/testing', $url);
+ }
+
+ public function testAbsoluteSecureUrlWithPort443()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+
+ $this->assertEquals('https://localhost/app.php/testing', $url);
+ }
+
+ public function testAbsoluteUrlWithNonStandardPort()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes, array('httpPort' => 8080))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+
+ $this->assertEquals('http://localhost:8080/app.php/testing', $url);
+ }
+
+ public function testAbsoluteSecureUrlWithNonStandardPort()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes, array('httpsPort' => 8080, 'scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+
+ $this->assertEquals('https://localhost:8080/app.php/testing', $url);
+ }
+
+ public function testRelativeUrlWithoutParameters()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('/app.php/testing', $url);
+ }
+
+ public function testRelativeUrlWithParameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}'));
+ $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('/app.php/testing/bar', $url);
+ }
+
+ public function testRelativeUrlWithNullParameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing.{format}', array('format' => null)));
+ $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('/app.php/testing', $url);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testRelativeUrlWithNullParameterButNotOptional()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}/bar', array('foo' => null)));
+ // This must raise an exception because the default requirement for "foo" is "[^/]+" which is not met with these params.
+ // Generating path "/testing//bar" would be wrong as matching this route would fail.
+ $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+ }
+
+ public function testRelativeUrlWithOptionalZeroParameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{page}'));
+ $url = $this->getGenerator($routes)->generate('test', array('page' => 0), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('/app.php/testing/0', $url);
+ }
+
+ public function testNotPassedOptionalParameterInBetween()
+ {
+ $routes = $this->getRoutes('test', new Route('/{slug}/{page}', array('slug' => 'index', 'page' => 0)));
+ $this->assertSame('/app.php/index/1', $this->getGenerator($routes)->generate('test', array('page' => 1)));
+ $this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test'));
+ }
+
+ public function testRelativeUrlWithExtraParameters()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('/app.php/testing?foo=bar', $url);
+ }
+
+ public function testAbsoluteUrlWithExtraParameters()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
+
+ $this->assertEquals('http://localhost/app.php/testing?foo=bar', $url);
+ }
+
+ public function testUrlWithNullExtraParameters()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes)->generate('test', array('foo' => null), UrlGeneratorInterface::ABSOLUTE_URL);
+
+ $this->assertEquals('http://localhost/app.php/testing', $url);
+ }
+
+ public function testUrlWithExtraParametersFromGlobals()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $generator = $this->getGenerator($routes);
+ $context = new RequestContext('/app.php');
+ $context->setParameter('bar', 'bar');
+ $generator->setContext($context);
+ $url = $generator->generate('test', array('foo' => 'bar'));
+
+ $this->assertEquals('/app.php/testing?foo=bar', $url);
+ }
+
+ public function testUrlWithGlobalParameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}'));
+ $generator = $this->getGenerator($routes);
+ $context = new RequestContext('/app.php');
+ $context->setParameter('foo', 'bar');
+ $generator->setContext($context);
+ $url = $generator->generate('test', array());
+
+ $this->assertEquals('/app.php/testing/bar', $url);
+ }
+
+ public function testGlobalParameterHasHigherPriorityThanDefault()
+ {
+ $routes = $this->getRoutes('test', new Route('/{_locale}', array('_locale' => 'en')));
+ $generator = $this->getGenerator($routes);
+ $context = new RequestContext('/app.php');
+ $context->setParameter('_locale', 'de');
+ $generator->setContext($context);
+ $url = $generator->generate('test', array());
+
+ $this->assertSame('/app.php/de', $url);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException
+ */
+ public function testGenerateWithoutRoutes()
+ {
+ $routes = $this->getRoutes('foo', new Route('/testing/{foo}'));
+ $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\MissingMandatoryParametersException
+ */
+ public function testGenerateForRouteWithoutMandatoryParameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}'));
+ $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testGenerateForRouteWithInvalidOptionalParameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testGenerateForRouteWithInvalidParameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '1|2')));
+ $this->getGenerator($routes)->generate('test', array('foo' => '0'), UrlGeneratorInterface::ABSOLUTE_URL);
+ }
+
+ public function testGenerateForRouteWithInvalidOptionalParameterNonStrict()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
+ $generator = $this->getGenerator($routes);
+ $generator->setStrictRequirements(false);
+ $this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL));
+ }
+
+ public function testGenerateForRouteWithInvalidOptionalParameterNonStrictWithLogger()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+ $logger->expects($this->once())
+ ->method('error');
+ $generator = $this->getGenerator($routes, array(), $logger);
+ $generator->setStrictRequirements(false);
+ $this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL));
+ }
+
+ public function testGenerateForRouteWithInvalidParameterButDisabledRequirementsCheck()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
+ $generator = $this->getGenerator($routes);
+ $generator->setStrictRequirements(null);
+ $this->assertSame('/app.php/testing/bar', $generator->generate('test', array('foo' => 'bar')));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testGenerateForRouteWithInvalidMandatoryParameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => 'd+')));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testGenerateForRouteWithInvalidUtf8Parameter()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '\pL+'), array('utf8' => true)));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'abc123'), UrlGeneratorInterface::ABSOLUTE_URL);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testRequiredParamAndEmptyPassed()
+ {
+ $routes = $this->getRoutes('test', new Route('/{slug}', array(), array('slug' => '.+')));
+ $this->getGenerator($routes)->generate('test', array('slug' => ''));
+ }
+
+ public function testSchemeRequirementDoesNothingIfSameCurrentScheme()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http')));
+ $this->assertEquals('/app.php/', $this->getGenerator($routes)->generate('test'));
+
+ $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https')));
+ $this->assertEquals('/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test'));
+ }
+
+ public function testSchemeRequirementForcesAbsoluteUrl()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https')));
+ $this->assertEquals('https://localhost/app.php/', $this->getGenerator($routes)->generate('test'));
+
+ $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http')));
+ $this->assertEquals('http://localhost/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test'));
+ }
+
+ public function testSchemeRequirementCreatesUrlForFirstRequiredScheme()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('Ftp', 'https')));
+ $this->assertEquals('ftp://localhost/app.php/', $this->getGenerator($routes)->generate('test'));
+ }
+
+ public function testPathWithTwoStartingSlashes()
+ {
+ $routes = $this->getRoutes('test', new Route('//path-and-not-domain'));
+
+ // this must not generate '//path-and-not-domain' because that would be a network path
+ $this->assertSame('/path-and-not-domain', $this->getGenerator($routes, array('BaseUrl' => ''))->generate('test'));
+ }
+
+ public function testNoTrailingSlashForMultipleOptionalParameters()
+ {
+ $routes = $this->getRoutes('test', new Route('/category/{slug1}/{slug2}/{slug3}', array('slug2' => null, 'slug3' => null)));
+
+ $this->assertEquals('/app.php/category/foo', $this->getGenerator($routes)->generate('test', array('slug1' => 'foo')));
+ }
+
+ public function testWithAnIntegerAsADefaultValue()
+ {
+ $routes = $this->getRoutes('test', new Route('/{default}', array('default' => 0)));
+
+ $this->assertEquals('/app.php/foo', $this->getGenerator($routes)->generate('test', array('default' => 'foo')));
+ }
+
+ public function testNullForOptionalParameterIsIgnored()
+ {
+ $routes = $this->getRoutes('test', new Route('/test/{default}', array('default' => 0)));
+
+ $this->assertEquals('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => null)));
+ }
+
+ public function testQueryParamSameAsDefault()
+ {
+ $routes = $this->getRoutes('test', new Route('/test', array('page' => 1)));
+
+ $this->assertSame('/app.php/test?page=2', $this->getGenerator($routes)->generate('test', array('page' => 2)));
+ $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => 1)));
+ $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => '1')));
+ $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test'));
+ }
+
+ public function testArrayQueryParamSameAsDefault()
+ {
+ $routes = $this->getRoutes('test', new Route('/test', array('array' => array('foo', 'bar'))));
+
+ $this->assertSame('/app.php/test?array%5B0%5D=bar&array%5B1%5D=foo', $this->getGenerator($routes)->generate('test', array('array' => array('bar', 'foo'))));
+ $this->assertSame('/app.php/test?array%5Ba%5D=foo&array%5Bb%5D=bar', $this->getGenerator($routes)->generate('test', array('array' => array('a' => 'foo', 'b' => 'bar'))));
+ $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array('foo', 'bar'))));
+ $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array(1 => 'bar', 0 => 'foo'))));
+ $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test'));
+ }
+
+ public function testGenerateWithSpecialRouteName()
+ {
+ $routes = $this->getRoutes('$péß^a|', new Route('/bar'));
+
+ $this->assertSame('/app.php/bar', $this->getGenerator($routes)->generate('$péß^a|'));
+ }
+
+ public function testUrlEncoding()
+ {
+ $expectedPath = '/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id'
+ .'/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id'
+ .'?query=%40%3A%5B%5D/%28%29%2A%27%22%20%2B%2C%3B-._~%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id';
+
+ // This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986)
+ // and other special ASCII chars. These chars are tested as static text path, variable path and query param.
+ $chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id';
+ $routes = $this->getRoutes('test', new Route("/$chars/{varpath}", array(), array('varpath' => '.+')));
+ $this->assertSame($expectedPath, $this->getGenerator($routes)->generate('test', array(
+ 'varpath' => $chars,
+ 'query' => $chars,
+ )));
+ }
+
+ public function testEncodingOfRelativePathSegments()
+ {
+ $routes = $this->getRoutes('test', new Route('/dir/../dir/..'));
+ $this->assertSame('/app.php/dir/%2E%2E/dir/%2E%2E', $this->getGenerator($routes)->generate('test'));
+ $routes = $this->getRoutes('test', new Route('/dir/./dir/.'));
+ $this->assertSame('/app.php/dir/%2E/dir/%2E', $this->getGenerator($routes)->generate('test'));
+ $routes = $this->getRoutes('test', new Route('/a./.a/a../..a/...'));
+ $this->assertSame('/app.php/a./.a/a../..a/...', $this->getGenerator($routes)->generate('test'));
+ }
+
+ public function testAdjacentVariables()
+ {
+ $routes = $this->getRoutes('test', new Route('/{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '\d+')));
+ $generator = $this->getGenerator($routes);
+ $this->assertSame('/app.php/foo123', $generator->generate('test', array('x' => 'foo', 'y' => '123')));
+ $this->assertSame('/app.php/foo123bar.xml', $generator->generate('test', array('x' => 'foo', 'y' => '123', 'z' => 'bar', '_format' => 'xml')));
+
+ // The default requirement for 'x' should not allow the separator '.' in this case because it would otherwise match everything
+ // and following optional variables like _format could never match.
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\InvalidParameterException');
+ $generator->generate('test', array('x' => 'do.t', 'y' => '123', 'z' => 'bar', '_format' => 'xml'));
+ }
+
+ public function testOptionalVariableWithNoRealSeparator()
+ {
+ $routes = $this->getRoutes('test', new Route('/get{what}', array('what' => 'All')));
+ $generator = $this->getGenerator($routes);
+
+ $this->assertSame('/app.php/get', $generator->generate('test'));
+ $this->assertSame('/app.php/getSites', $generator->generate('test', array('what' => 'Sites')));
+ }
+
+ public function testRequiredVariableWithNoRealSeparator()
+ {
+ $routes = $this->getRoutes('test', new Route('/get{what}Suffix'));
+ $generator = $this->getGenerator($routes);
+
+ $this->assertSame('/app.php/getSitesSuffix', $generator->generate('test', array('what' => 'Sites')));
+ }
+
+ public function testDefaultRequirementOfVariable()
+ {
+ $routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
+ $generator = $this->getGenerator($routes);
+
+ $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html')));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testDefaultRequirementOfVariableDisallowsSlash()
+ {
+ $routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
+ $this->getGenerator($routes)->generate('test', array('page' => 'index', '_format' => 'sl/ash'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testDefaultRequirementOfVariableDisallowsNextSeparator()
+ {
+ $routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
+ $this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html'));
+ }
+
+ public function testWithHostDifferentFromContext()
+ {
+ $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
+
+ $this->assertEquals('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' => 'Fabien', 'locale' => 'fr')));
+ }
+
+ public function testWithHostSameAsContext()
+ {
+ $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
+
+ $this->assertEquals('/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr')));
+ }
+
+ public function testWithHostSameAsContextAndAbsolute()
+ {
+ $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
+
+ $this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testUrlWithInvalidParameterInHost()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com'));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testUrlWithInvalidParameterInHostWhenParamHasADefaultValue()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array('foo' => 'bar'), array('foo' => 'bar'), array(), '{foo}.example.com'));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
+ */
+ public function testUrlWithInvalidParameterEqualsDefaultValueInHost()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array('foo' => 'baz'), array('foo' => 'bar'), array(), '{foo}.example.com'));
+ $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ }
+
+ public function testUrlWithInvalidParameterInHostInNonStrictMode()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com'));
+ $generator = $this->getGenerator($routes);
+ $generator->setStrictRequirements(false);
+ $this->assertNull($generator->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH));
+ }
+
+ public function testHostIsCaseInsensitive()
+ {
+ $routes = $this->getRoutes('test', new Route('/', array(), array('locale' => 'en|de|fr'), array(), '{locale}.FooBar.com'));
+ $generator = $this->getGenerator($routes);
+ $this->assertSame('//EN.FooBar.com/app.php/', $generator->generate('test', array('locale' => 'EN'), UrlGeneratorInterface::NETWORK_PATH));
+ }
+
+ public function testDefaultHostIsUsedWhenContextHostIsEmpty()
+ {
+ $routes = $this->getRoutes('test', new Route('/route', array('domain' => 'my.fallback.host'), array('domain' => '.+'), array(), '{domain}', array('http')));
+
+ $generator = $this->getGenerator($routes);
+ $generator->getContext()->setHost('');
+
+ $this->assertSame('http://my.fallback.host/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL));
+ }
+
+ public function testDefaultHostIsUsedWhenContextHostIsEmptyAndSchemeIsNot()
+ {
+ $routes = $this->getRoutes('test', new Route('/route', array('domain' => 'my.fallback.host'), array('domain' => '.+'), array(), '{domain}', array('http', 'https')));
+
+ $generator = $this->getGenerator($routes);
+ $generator->getContext()->setHost('');
+ $generator->getContext()->setScheme('https');
+
+ $this->assertSame('https://my.fallback.host/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL));
+ }
+
+ public function testAbsoluteUrlFallbackToRelativeIfHostIsEmptyAndSchemeIsNot()
+ {
+ $routes = $this->getRoutes('test', new Route('/route', array(), array(), array(), '', array('http', 'https')));
+
+ $generator = $this->getGenerator($routes);
+ $generator->getContext()->setHost('');
+ $generator->getContext()->setScheme('https');
+
+ $this->assertSame('/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL));
+ }
+
+ public function testGenerateNetworkPath()
+ {
+ $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com', array('http')));
+
+ $this->assertSame('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test',
+ array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'network path with different host'
+ );
+ $this->assertSame('//fr.example.com/app.php/Fabien?query=string', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test',
+ array('name' => 'Fabien', 'locale' => 'fr', 'query' => 'string'), UrlGeneratorInterface::NETWORK_PATH), 'network path although host same as context'
+ );
+ $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test',
+ array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'absolute URL because scheme requirement does not match context'
+ );
+ $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test',
+ array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL), 'absolute URL with same scheme because it is requested'
+ );
+ }
+
+ public function testGenerateRelativePath()
+ {
+ $routes = new RouteCollection();
+ $routes->add('article', new Route('/{author}/{article}/'));
+ $routes->add('comments', new Route('/{author}/{article}/comments'));
+ $routes->add('host', new Route('/{article}', array(), array(), array(), '{author}.example.com'));
+ $routes->add('scheme', new Route('/{author}/blog', array(), array(), array(), '', array('https')));
+ $routes->add('unrelated', new Route('/about'));
+
+ $generator = $this->getGenerator($routes, array('host' => 'example.com', 'pathInfo' => '/fabien/symfony-is-great/'));
+
+ $this->assertSame('comments', $generator->generate('comments',
+ array('author' => 'fabien', 'article' => 'symfony-is-great'), UrlGeneratorInterface::RELATIVE_PATH)
+ );
+ $this->assertSame('comments?page=2', $generator->generate('comments',
+ array('author' => 'fabien', 'article' => 'symfony-is-great', 'page' => 2), UrlGeneratorInterface::RELATIVE_PATH)
+ );
+ $this->assertSame('../twig-is-great/', $generator->generate('article',
+ array('author' => 'fabien', 'article' => 'twig-is-great'), UrlGeneratorInterface::RELATIVE_PATH)
+ );
+ $this->assertSame('../../bernhard/forms-are-great/', $generator->generate('article',
+ array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH)
+ );
+ $this->assertSame('//bernhard.example.com/app.php/forms-are-great', $generator->generate('host',
+ array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH)
+ );
+ $this->assertSame('https://example.com/app.php/bernhard/blog', $generator->generate('scheme',
+ array('author' => 'bernhard'), UrlGeneratorInterface::RELATIVE_PATH)
+ );
+ $this->assertSame('../../about', $generator->generate('unrelated',
+ array(), UrlGeneratorInterface::RELATIVE_PATH)
+ );
+ }
+
+ /**
+ * @dataProvider provideRelativePaths
+ */
+ public function testGetRelativePath($sourcePath, $targetPath, $expectedPath)
+ {
+ $this->assertSame($expectedPath, UrlGenerator::getRelativePath($sourcePath, $targetPath));
+ }
+
+ public function provideRelativePaths()
+ {
+ return array(
+ array(
+ '/same/dir/',
+ '/same/dir/',
+ '',
+ ),
+ array(
+ '/same/file',
+ '/same/file',
+ '',
+ ),
+ array(
+ '/',
+ '/file',
+ 'file',
+ ),
+ array(
+ '/',
+ '/dir/file',
+ 'dir/file',
+ ),
+ array(
+ '/dir/file.html',
+ '/dir/different-file.html',
+ 'different-file.html',
+ ),
+ array(
+ '/same/dir/extra-file',
+ '/same/dir/',
+ './',
+ ),
+ array(
+ '/parent/dir/',
+ '/parent/',
+ '../',
+ ),
+ array(
+ '/parent/dir/extra-file',
+ '/parent/',
+ '../',
+ ),
+ array(
+ '/a/b/',
+ '/x/y/z/',
+ '../../x/y/z/',
+ ),
+ array(
+ '/a/b/c/d/e',
+ '/a/c/d',
+ '../../../c/d',
+ ),
+ array(
+ '/a/b/c//',
+ '/a/b/c/',
+ '../',
+ ),
+ array(
+ '/a/b/c/',
+ '/a/b/c//',
+ './/',
+ ),
+ array(
+ '/root/a/b/c/',
+ '/root/x/b/c/',
+ '../../../x/b/c/',
+ ),
+ array(
+ '/a/b/c/d/',
+ '/a',
+ '../../../../a',
+ ),
+ array(
+ '/special-chars/sp%20ce/1€/mäh/e=mc²',
+ '/special-chars/sp%20ce/1€/<µ>/e=mc²',
+ '../<µ>/e=mc²',
+ ),
+ array(
+ 'not-rooted',
+ 'dir/file',
+ 'dir/file',
+ ),
+ array(
+ '//dir/',
+ '',
+ '../../',
+ ),
+ array(
+ '/dir/',
+ '/dir/file:with-colon',
+ './file:with-colon',
+ ),
+ array(
+ '/dir/',
+ '/dir/subdir/file:with-colon',
+ 'subdir/file:with-colon',
+ ),
+ array(
+ '/dir/',
+ '/dir/:subdir/',
+ './:subdir/',
+ ),
+ );
+ }
+
+ public function testFragmentsCanBeAppendedToUrls()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+
+ $url = $this->getGenerator($routes)->generate('test', array('_fragment' => 'frag ment'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ $this->assertEquals('/app.php/testing#frag%20ment', $url);
+
+ $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '0'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ $this->assertEquals('/app.php/testing#0', $url);
+ }
+
+ public function testFragmentsDoNotEscapeValidCharacters()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing'));
+ $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '?/'), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('/app.php/testing#?/', $url);
+ }
+
+ public function testFragmentsCanBeDefinedAsDefaults()
+ {
+ $routes = $this->getRoutes('test', new Route('/testing', array('_fragment' => 'fragment')));
+ $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('/app.php/testing#fragment', $url);
+ }
+
+ protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null)
+ {
+ $context = new RequestContext('/app.php');
+ foreach ($parameters as $key => $value) {
+ $method = 'set'.$key;
+ $context->$method($value);
+ }
+
+ return new UrlGenerator($routes, $context, $logger);
+ }
+
+ protected function getRoutes($name, Route $route)
+ {
+ $routes = new RouteCollection();
+ $routes->add($name, $route);
+
+ return $routes;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php
new file mode 100644
index 0000000..e8bbe8f
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+
+abstract class AbstractAnnotationLoaderTest extends TestCase
+{
+ public function getReader()
+ {
+ return $this->getMockBuilder('Doctrine\Common\Annotations\Reader')
+ ->disableOriginalConstructor()
+ ->getMock()
+ ;
+ }
+
+ public function getClassLoader($reader)
+ {
+ return $this->getMockBuilder('Symfony\Component\Routing\Loader\AnnotationClassLoader')
+ ->setConstructorArgs(array($reader))
+ ->getMockForAbstractClass()
+ ;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php
new file mode 100644
index 0000000..70db1cc
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php
@@ -0,0 +1,255 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use Symfony\Component\Routing\Annotation\Route;
+
+class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
+{
+ protected $loader;
+ private $reader;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->reader = $this->getReader();
+ $this->loader = $this->getClassLoader($this->reader);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testLoadMissingClass()
+ {
+ $this->loader->load('MissingClass');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testLoadAbstractClass()
+ {
+ $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\AbstractClass');
+ }
+
+ /**
+ * @dataProvider provideTestSupportsChecksResource
+ */
+ public function testSupportsChecksResource($resource, $expectedSupports)
+ {
+ $this->assertSame($expectedSupports, $this->loader->supports($resource), '->supports() returns true if the resource is loadable');
+ }
+
+ public function provideTestSupportsChecksResource()
+ {
+ return array(
+ array('class', true),
+ array('\fully\qualified\class\name', true),
+ array('namespaced\class\without\leading\slash', true),
+ array('ÿClassWithLegalSpecialCharacters', true),
+ array('5', false),
+ array('foo.foo', false),
+ array(null, false),
+ );
+ }
+
+ public function testSupportsChecksTypeIfSpecified()
+ {
+ $this->assertTrue($this->loader->supports('class', 'annotation'), '->supports() checks the resource type if specified');
+ $this->assertFalse($this->loader->supports('class', 'foo'), '->supports() checks the resource type if specified');
+ }
+
+ public function getLoadTests()
+ {
+ return array(
+ array(
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
+ array('name' => 'route1', 'path' => '/path'),
+ array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
+ ),
+ array(
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
+ array('defaults' => array('arg2' => 'foo'), 'requirements' => array('arg3' => '\w+')),
+ array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
+ ),
+ array(
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
+ array('options' => array('foo' => 'bar')),
+ array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
+ ),
+ array(
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
+ array('schemes' => array('https'), 'methods' => array('GET')),
+ array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
+ ),
+ array(
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
+ array('condition' => 'context.getMethod() == "GET"'),
+ array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getLoadTests
+ */
+ public function testLoad($className, $routeData = array(), $methodArgs = array())
+ {
+ $routeData = array_replace(array(
+ 'name' => 'route',
+ 'path' => '/',
+ 'requirements' => array(),
+ 'options' => array(),
+ 'defaults' => array(),
+ 'schemes' => array(),
+ 'methods' => array(),
+ 'condition' => '',
+ ), $routeData);
+
+ $this->reader
+ ->expects($this->once())
+ ->method('getMethodAnnotations')
+ ->will($this->returnValue(array($this->getAnnotatedRoute($routeData))))
+ ;
+
+ $routeCollection = $this->loader->load($className);
+ $route = $routeCollection->get($routeData['name']);
+
+ $this->assertSame($routeData['path'], $route->getPath(), '->load preserves path annotation');
+ $this->assertCount(
+ count($routeData['requirements']),
+ array_intersect_assoc($routeData['requirements'], $route->getRequirements()),
+ '->load preserves requirements annotation'
+ );
+ $this->assertCount(
+ count($routeData['options']),
+ array_intersect_assoc($routeData['options'], $route->getOptions()),
+ '->load preserves options annotation'
+ );
+ $this->assertCount(
+ count($routeData['defaults']),
+ $route->getDefaults(),
+ '->load preserves defaults annotation'
+ );
+ $this->assertEquals($routeData['schemes'], $route->getSchemes(), '->load preserves schemes annotation');
+ $this->assertEquals($routeData['methods'], $route->getMethods(), '->load preserves methods annotation');
+ $this->assertSame($routeData['condition'], $route->getCondition(), '->load preserves condition annotation');
+ }
+
+ public function testClassRouteLoad()
+ {
+ $classRouteData = array(
+ 'name' => 'prefix_',
+ 'path' => '/prefix',
+ 'schemes' => array('https'),
+ 'methods' => array('GET'),
+ );
+
+ $methodRouteData = array(
+ 'name' => 'route1',
+ 'path' => '/path',
+ 'schemes' => array('http'),
+ 'methods' => array('POST', 'PUT'),
+ );
+
+ $this->reader
+ ->expects($this->once())
+ ->method('getClassAnnotation')
+ ->will($this->returnValue($this->getAnnotatedRoute($classRouteData)))
+ ;
+ $this->reader
+ ->expects($this->once())
+ ->method('getMethodAnnotations')
+ ->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData))))
+ ;
+
+ $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass');
+ $route = $routeCollection->get($classRouteData['name'].$methodRouteData['name']);
+
+ $this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path');
+ $this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes');
+ $this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods');
+ }
+
+ public function testInvokableClassRouteLoad()
+ {
+ $classRouteData = array(
+ 'name' => 'route1',
+ 'path' => '/',
+ 'schemes' => array('https'),
+ 'methods' => array('GET'),
+ );
+
+ $this->reader
+ ->expects($this->exactly(2))
+ ->method('getClassAnnotation')
+ ->will($this->returnValue($this->getAnnotatedRoute($classRouteData)))
+ ;
+ $this->reader
+ ->expects($this->once())
+ ->method('getMethodAnnotations')
+ ->will($this->returnValue(array()))
+ ;
+
+ $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass');
+ $route = $routeCollection->get($classRouteData['name']);
+
+ $this->assertSame($classRouteData['path'], $route->getPath(), '->load preserves class route path');
+ $this->assertEquals(array_merge($classRouteData['schemes'], $classRouteData['schemes']), $route->getSchemes(), '->load preserves class route schemes');
+ $this->assertEquals(array_merge($classRouteData['methods'], $classRouteData['methods']), $route->getMethods(), '->load preserves class route methods');
+ }
+
+ public function testInvokableClassWithMethodRouteLoad()
+ {
+ $classRouteData = array(
+ 'name' => 'route1',
+ 'path' => '/prefix',
+ 'schemes' => array('https'),
+ 'methods' => array('GET'),
+ );
+
+ $methodRouteData = array(
+ 'name' => 'route2',
+ 'path' => '/path',
+ 'schemes' => array('http'),
+ 'methods' => array('POST', 'PUT'),
+ );
+
+ $this->reader
+ ->expects($this->once())
+ ->method('getClassAnnotation')
+ ->will($this->returnValue($this->getAnnotatedRoute($classRouteData)))
+ ;
+ $this->reader
+ ->expects($this->once())
+ ->method('getMethodAnnotations')
+ ->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData))))
+ ;
+
+ $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass');
+ $route = $routeCollection->get($classRouteData['name']);
+
+ $this->assertNull($route, '->load ignores class route');
+
+ $route = $routeCollection->get($classRouteData['name'].$methodRouteData['name']);
+
+ $this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path');
+ $this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes');
+ $this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods');
+ }
+
+ private function getAnnotatedRoute($data)
+ {
+ return new Route($data);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php
new file mode 100644
index 0000000..1e8ee39
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
+use Symfony\Component\Config\FileLocator;
+
+class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest
+{
+ protected $loader;
+ protected $reader;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->reader = $this->getReader();
+ $this->loader = new AnnotationDirectoryLoader(new FileLocator(), $this->getClassLoader($this->reader));
+ }
+
+ public function testLoad()
+ {
+ $this->reader->expects($this->exactly(4))->method('getClassAnnotation');
+
+ $this->reader
+ ->expects($this->any())
+ ->method('getMethodAnnotations')
+ ->will($this->returnValue(array()))
+ ;
+
+ $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses');
+ }
+
+ public function testLoadIgnoresHiddenDirectories()
+ {
+ $this->expectAnnotationsToBeReadFrom(array(
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass',
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass',
+ 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\FooClass',
+ ));
+
+ $this->reader
+ ->expects($this->any())
+ ->method('getMethodAnnotations')
+ ->will($this->returnValue(array()))
+ ;
+
+ $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses');
+ }
+
+ public function testSupports()
+ {
+ $fixturesDir = __DIR__.'/../Fixtures';
+
+ $this->assertTrue($this->loader->supports($fixturesDir), '->supports() returns true if the resource is loadable');
+ $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable');
+
+ $this->assertTrue($this->loader->supports($fixturesDir, 'annotation'), '->supports() checks the resource type if specified');
+ $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports() checks the resource type if specified');
+ }
+
+ public function testItSupportsAnyAnnotation()
+ {
+ $this->assertTrue($this->loader->supports(__DIR__.'/../Fixtures/even-with-not-existing-folder', 'annotation'));
+ }
+
+ public function testLoadFileIfLocatedResourceIsFile()
+ {
+ $this->reader->expects($this->exactly(1))->method('getClassAnnotation');
+
+ $this->reader
+ ->expects($this->any())
+ ->method('getMethodAnnotations')
+ ->will($this->returnValue(array()))
+ ;
+
+ $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php');
+ }
+
+ private function expectAnnotationsToBeReadFrom(array $classes)
+ {
+ $this->reader->expects($this->exactly(count($classes)))
+ ->method('getClassAnnotation')
+ ->with($this->callback(function (\ReflectionClass $class) use ($classes) {
+ return in_array($class->getName(), $classes);
+ }));
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php
new file mode 100644
index 0000000..7f1d576
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php
@@ -0,0 +1,91 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use Symfony\Component\Routing\Loader\AnnotationFileLoader;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Routing\Annotation\Route;
+
+class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest
+{
+ protected $loader;
+ protected $reader;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->reader = $this->getReader();
+ $this->loader = new AnnotationFileLoader(new FileLocator(), $this->getClassLoader($this->reader));
+ }
+
+ public function testLoad()
+ {
+ $this->reader->expects($this->once())->method('getClassAnnotation');
+
+ $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php');
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testLoadTraitWithClassConstant()
+ {
+ $this->reader->expects($this->never())->method('getClassAnnotation');
+
+ $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooTrait.php');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Did you forgot to add the "loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/NoStartTagClass.php');
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testLoadVariadic()
+ {
+ $route = new Route(array('path' => '/path/to/{id}'));
+ $this->reader->expects($this->once())->method('getClassAnnotation');
+ $this->reader->expects($this->once())->method('getMethodAnnotations')
+ ->will($this->returnValue(array($route)));
+
+ $this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/VariadicClass.php');
+ }
+
+ /**
+ * @requires PHP 7.0
+ */
+ public function testLoadAnonymousClass()
+ {
+ $this->reader->expects($this->never())->method('getClassAnnotation');
+ $this->reader->expects($this->never())->method('getMethodAnnotations');
+
+ $this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php');
+ }
+
+ public function testSupports()
+ {
+ $fixture = __DIR__.'/../Fixtures/annotated.php';
+
+ $this->assertTrue($this->loader->supports($fixture), '->supports() returns true if the resource is loadable');
+ $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable');
+
+ $this->assertTrue($this->loader->supports($fixture, 'annotation'), '->supports() checks the resource type if specified');
+ $this->assertFalse($this->loader->supports($fixture, 'foo'), '->supports() checks the resource type if specified');
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php
new file mode 100644
index 0000000..5d963f8
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Loader\ClosureLoader;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+class ClosureLoaderTest extends TestCase
+{
+ public function testSupports()
+ {
+ $loader = new ClosureLoader();
+
+ $closure = function () {};
+
+ $this->assertTrue($loader->supports($closure), '->supports() returns true if the resource is loadable');
+ $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable');
+
+ $this->assertTrue($loader->supports($closure, 'closure'), '->supports() checks the resource type if specified');
+ $this->assertFalse($loader->supports($closure, 'foo'), '->supports() checks the resource type if specified');
+ }
+
+ public function testLoad()
+ {
+ $loader = new ClosureLoader();
+
+ $route = new Route('/');
+ $routes = $loader->load(function () use ($route) {
+ $routes = new RouteCollection();
+
+ $routes->add('foo', $route);
+
+ return $routes;
+ });
+
+ $this->assertEquals($route, $routes->get('foo'), '->load() loads a \Closure resource');
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php
new file mode 100644
index 0000000..fc29d37
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use Symfony\Component\Routing\Loader\DirectoryLoader;
+use Symfony\Component\Routing\Loader\YamlFileLoader;
+use Symfony\Component\Routing\Loader\AnnotationFileLoader;
+use Symfony\Component\Config\Loader\LoaderResolver;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Routing\RouteCollection;
+
+class DirectoryLoaderTest extends AbstractAnnotationLoaderTest
+{
+ private $loader;
+ private $reader;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $locator = new FileLocator();
+ $this->reader = $this->getReader();
+ $this->loader = new DirectoryLoader($locator);
+ $resolver = new LoaderResolver(array(
+ new YamlFileLoader($locator),
+ new AnnotationFileLoader($locator, $this->getClassLoader($this->reader)),
+ $this->loader,
+ ));
+ $this->loader->setResolver($resolver);
+ }
+
+ public function testLoadDirectory()
+ {
+ $collection = $this->loader->load(__DIR__.'/../Fixtures/directory', 'directory');
+ $this->verifyCollection($collection);
+ }
+
+ public function testImportDirectory()
+ {
+ $collection = $this->loader->load(__DIR__.'/../Fixtures/directory_import', 'directory');
+ $this->verifyCollection($collection);
+ }
+
+ private function verifyCollection(RouteCollection $collection)
+ {
+ $routes = $collection->all();
+
+ $this->assertCount(3, $routes, 'Three routes are loaded');
+ $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
+
+ for ($i = 1; $i <= 3; ++$i) {
+ $this->assertSame('/route/'.$i, $routes['route'.$i]->getPath());
+ }
+ }
+
+ public function testSupports()
+ {
+ $fixturesDir = __DIR__.'/../Fixtures';
+
+ $this->assertFalse($this->loader->supports($fixturesDir), '->supports(*) returns false');
+
+ $this->assertTrue($this->loader->supports($fixturesDir, 'directory'), '->supports(*, "directory") returns true');
+ $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports(*, "foo") returns false');
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php
new file mode 100644
index 0000000..08d806a
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\GlobResource;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Routing\Loader\GlobFileLoader;
+use Symfony\Component\Routing\RouteCollection;
+
+class GlobFileLoaderTest extends TestCase
+{
+ public function testSupports()
+ {
+ $loader = new GlobFileLoader(new FileLocator());
+
+ $this->assertTrue($loader->supports('any-path', 'glob'), '->supports() returns true if the resource has the glob type');
+ $this->assertFalse($loader->supports('any-path'), '->supports() returns false if the resource is not of glob type');
+ }
+
+ public function testLoadAddsTheGlobResourceToTheContainer()
+ {
+ $loader = new GlobFileLoaderWithoutImport(new FileLocator());
+ $collection = $loader->load(__DIR__.'/../Fixtures/directory/*.yml');
+
+ $this->assertEquals(new GlobResource(__DIR__.'/../Fixtures/directory', '/*.yml', false), $collection->getResources()[0]);
+ }
+}
+
+class GlobFileLoaderWithoutImport extends GlobFileLoader
+{
+ public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null)
+ {
+ return new RouteCollection();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php
new file mode 100644
index 0000000..408fa0b
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php
@@ -0,0 +1,123 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Loader\ObjectRouteLoader;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+class ObjectRouteLoaderTest extends TestCase
+{
+ public function testLoadCallsServiceAndReturnsCollection()
+ {
+ $loader = new ObjectRouteLoaderForTest();
+
+ // create a basic collection that will be returned
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo'));
+
+ $loader->loaderMap = array(
+ 'my_route_provider_service' => new RouteService($collection),
+ );
+
+ $actualRoutes = $loader->load(
+ 'my_route_provider_service:loadRoutes',
+ 'service'
+ );
+
+ $this->assertSame($collection, $actualRoutes);
+ // the service file should be listed as a resource
+ $this->assertNotEmpty($actualRoutes->getResources());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @dataProvider getBadResourceStrings
+ */
+ public function testExceptionWithoutSyntax($resourceString)
+ {
+ $loader = new ObjectRouteLoaderForTest();
+ $loader->load($resourceString);
+ }
+
+ public function getBadResourceStrings()
+ {
+ return array(
+ array('Foo'),
+ array('Bar::baz'),
+ array('Foo:Bar:baz'),
+ );
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testExceptionOnNoObjectReturned()
+ {
+ $loader = new ObjectRouteLoaderForTest();
+ $loader->loaderMap = array('my_service' => 'NOT_AN_OBJECT');
+ $loader->load('my_service:method');
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testExceptionOnBadMethod()
+ {
+ $loader = new ObjectRouteLoaderForTest();
+ $loader->loaderMap = array('my_service' => new \stdClass());
+ $loader->load('my_service:method');
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testExceptionOnMethodNotReturningCollection()
+ {
+ $service = $this->getMockBuilder('stdClass')
+ ->setMethods(array('loadRoutes'))
+ ->getMock();
+ $service->expects($this->once())
+ ->method('loadRoutes')
+ ->will($this->returnValue('NOT_A_COLLECTION'));
+
+ $loader = new ObjectRouteLoaderForTest();
+ $loader->loaderMap = array('my_service' => $service);
+ $loader->load('my_service:loadRoutes');
+ }
+}
+
+class ObjectRouteLoaderForTest extends ObjectRouteLoader
+{
+ public $loaderMap = array();
+
+ protected function getServiceObject($id)
+ {
+ return isset($this->loaderMap[$id]) ? $this->loaderMap[$id] : null;
+ }
+}
+
+class RouteService
+{
+ private $collection;
+
+ public function __construct($collection)
+ {
+ $this->collection = $collection;
+ }
+
+ public function loadRoutes()
+ {
+ return $this->collection;
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php
new file mode 100644
index 0000000..0dcf5d4
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php
@@ -0,0 +1,133 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Routing\Loader\PhpFileLoader;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+class PhpFileLoaderTest extends TestCase
+{
+ public function testSupports()
+ {
+ $loader = new PhpFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock());
+
+ $this->assertTrue($loader->supports('foo.php'), '->supports() returns true if the resource is loadable');
+ $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable');
+
+ $this->assertTrue($loader->supports('foo.php', 'php'), '->supports() checks the resource type if specified');
+ $this->assertFalse($loader->supports('foo.php', 'foo'), '->supports() checks the resource type if specified');
+ }
+
+ public function testLoadWithRoute()
+ {
+ $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('validpattern.php');
+ $routes = $routeCollection->all();
+
+ $this->assertCount(1, $routes, 'One route is loaded');
+ $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
+
+ foreach ($routes as $route) {
+ $this->assertSame('/blog/{slug}', $route->getPath());
+ $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertSame('{locale}.example.com', $route->getHost());
+ $this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
+ $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods());
+ $this->assertEquals(array('https'), $route->getSchemes());
+ }
+ }
+
+ public function testLoadWithImport()
+ {
+ $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('validresource.php');
+ $routes = $routeCollection->all();
+
+ $this->assertCount(1, $routes, 'One route is loaded');
+ $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
+
+ foreach ($routes as $route) {
+ $this->assertSame('/prefix/blog/{slug}', $route->getPath());
+ $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertSame('{locale}.example.com', $route->getHost());
+ $this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
+ $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods());
+ $this->assertEquals(array('https'), $route->getSchemes());
+ }
+ }
+
+ public function testThatDefiningVariableInConfigFileHasNoSideEffects()
+ {
+ $locator = new FileLocator(array(__DIR__.'/../Fixtures'));
+ $loader = new PhpFileLoader($locator);
+ $routeCollection = $loader->load('with_define_path_variable.php');
+ $resources = $routeCollection->getResources();
+ $this->assertCount(1, $resources);
+ $this->assertContainsOnly('Symfony\Component\Config\Resource\ResourceInterface', $resources);
+ $fileResource = reset($resources);
+ $this->assertSame(
+ realpath($locator->locate('with_define_path_variable.php')),
+ (string) $fileResource
+ );
+ }
+
+ public function testRoutingConfigurator()
+ {
+ $locator = new FileLocator(array(__DIR__.'/../Fixtures'));
+ $loader = new PhpFileLoader($locator);
+ $routeCollection = $loader->load('php_dsl.php');
+
+ $expectedCollection = new RouteCollection();
+
+ $expectedCollection->add('foo', (new Route('/foo'))
+ ->setOptions(array('utf8' => true))
+ ->setCondition('abc')
+ );
+ $expectedCollection->add('buz', (new Route('/zub'))
+ ->setDefaults(array('_controller' => 'foo:act'))
+ );
+ $expectedCollection->add('c_bar', (new Route('/sub/pub/bar'))
+ ->setRequirements(array('id' => '\d+'))
+ );
+ $expectedCollection->add('c_pub_buz', (new Route('/sub/pub/buz'))
+ ->setHost('host')
+ ->setRequirements(array('id' => '\d+'))
+ );
+ $expectedCollection->add('ouf', (new Route('/ouf'))
+ ->setSchemes(array('https'))
+ ->setMethods(array('GET'))
+ ->setDefaults(array('id' => 0))
+ );
+
+ $expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub.php')));
+ $expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl.php')));
+
+ $this->assertEquals($expectedCollection, $routeCollection);
+ }
+
+ public function testRoutingConfiguratorCanImportGlobPatterns()
+ {
+ $locator = new FileLocator(array(__DIR__.'/../Fixtures/glob'));
+ $loader = new PhpFileLoader($locator);
+ $routeCollection = $loader->load('php_dsl.php');
+
+ $route = $routeCollection->get('bar_route');
+ $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller'));
+
+ $route = $routeCollection->get('baz_route');
+ $this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller'));
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php
new file mode 100644
index 0000000..21fc340
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php
@@ -0,0 +1,385 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Routing\Loader\XmlFileLoader;
+use Symfony\Component\Routing\Tests\Fixtures\CustomXmlFileLoader;
+
+class XmlFileLoaderTest extends TestCase
+{
+ public function testSupports()
+ {
+ $loader = new XmlFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock());
+
+ $this->assertTrue($loader->supports('foo.xml'), '->supports() returns true if the resource is loadable');
+ $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable');
+
+ $this->assertTrue($loader->supports('foo.xml', 'xml'), '->supports() checks the resource type if specified');
+ $this->assertFalse($loader->supports('foo.xml', 'foo'), '->supports() checks the resource type if specified');
+ }
+
+ public function testLoadWithRoute()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('validpattern.xml');
+ $route = $routeCollection->get('blog_show');
+
+ $this->assertInstanceOf('Symfony\Component\Routing\Route', $route);
+ $this->assertSame('/blog/{slug}', $route->getPath());
+ $this->assertSame('{locale}.example.com', $route->getHost());
+ $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertSame('\w+', $route->getRequirement('locale'));
+ $this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
+ $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods());
+ $this->assertEquals(array('https'), $route->getSchemes());
+ $this->assertEquals('context.getMethod() == "GET"', $route->getCondition());
+ }
+
+ public function testLoadWithNamespacePrefix()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('namespaceprefix.xml');
+
+ $this->assertCount(1, $routeCollection->all(), 'One route is loaded');
+
+ $route = $routeCollection->get('blog_show');
+ $this->assertSame('/blog/{slug}', $route->getPath());
+ $this->assertSame('{_locale}.example.com', $route->getHost());
+ $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertSame('\w+', $route->getRequirement('slug'));
+ $this->assertSame('en|fr|de', $route->getRequirement('_locale'));
+ $this->assertNull($route->getDefault('slug'));
+ $this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
+ $this->assertSame(1, $route->getDefault('page'));
+ }
+
+ public function testLoadWithImport()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('validresource.xml');
+ $routes = $routeCollection->all();
+
+ $this->assertCount(2, $routes, 'Two routes are loaded');
+ $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
+
+ foreach ($routes as $route) {
+ $this->assertSame('/{foo}/blog/{slug}', $route->getPath());
+ $this->assertSame('123', $route->getDefault('foo'));
+ $this->assertSame('\d+', $route->getRequirement('foo'));
+ $this->assertSame('bar', $route->getOption('foo'));
+ $this->assertSame('', $route->getHost());
+ $this->assertSame('context.getMethod() == "POST"', $route->getCondition());
+ }
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @dataProvider getPathsToInvalidFiles
+ */
+ public function testLoadThrowsExceptionWithInvalidFile($filePath)
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $loader->load($filePath);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @dataProvider getPathsToInvalidFiles
+ */
+ public function testLoadThrowsExceptionWithInvalidFileEvenWithoutSchemaValidation($filePath)
+ {
+ $loader = new CustomXmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $loader->load($filePath);
+ }
+
+ public function getPathsToInvalidFiles()
+ {
+ return array(array('nonvalidnode.xml'), array('nonvalidroute.xml'), array('nonvalid.xml'), array('missing_id.xml'), array('missing_path.xml'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Document types are not allowed.
+ */
+ public function testDocTypeIsNotAllowed()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $loader->load('withdoctype.xml');
+ }
+
+ public function testNullValues()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('null_values.xml');
+ $route = $routeCollection->get('blog_show');
+
+ $this->assertTrue($route->hasDefault('foo'));
+ $this->assertNull($route->getDefault('foo'));
+ $this->assertTrue($route->hasDefault('bar'));
+ $this->assertNull($route->getDefault('bar'));
+ $this->assertEquals('foo', $route->getDefault('foobar'));
+ $this->assertEquals('bar', $route->getDefault('baz'));
+ }
+
+ public function testScalarDataTypeDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('scalar_defaults.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(
+ array(
+ '_controller' => 'AcmeBlogBundle:Blog:index',
+ 'slug' => null,
+ 'published' => true,
+ 'page' => 1,
+ 'price' => 3.5,
+ 'archived' => false,
+ 'free' => true,
+ 'locked' => false,
+ 'foo' => null,
+ 'bar' => null,
+ ),
+ $route->getDefaults()
+ );
+ }
+
+ public function testListDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('list_defaults.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(
+ array(
+ '_controller' => 'AcmeBlogBundle:Blog:index',
+ 'values' => array(true, 1, 3.5, 'foo'),
+ ),
+ $route->getDefaults()
+ );
+ }
+
+ public function testListInListDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('list_in_list_defaults.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(
+ array(
+ '_controller' => 'AcmeBlogBundle:Blog:index',
+ 'values' => array(array(true, 1, 3.5, 'foo')),
+ ),
+ $route->getDefaults()
+ );
+ }
+
+ public function testListInMapDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('list_in_map_defaults.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(
+ array(
+ '_controller' => 'AcmeBlogBundle:Blog:index',
+ 'values' => array('list' => array(true, 1, 3.5, 'foo')),
+ ),
+ $route->getDefaults()
+ );
+ }
+
+ public function testMapDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('map_defaults.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(
+ array(
+ '_controller' => 'AcmeBlogBundle:Blog:index',
+ 'values' => array(
+ 'public' => true,
+ 'page' => 1,
+ 'price' => 3.5,
+ 'title' => 'foo',
+ ),
+ ),
+ $route->getDefaults()
+ );
+ }
+
+ public function testMapInListDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('map_in_list_defaults.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(
+ array(
+ '_controller' => 'AcmeBlogBundle:Blog:index',
+ 'values' => array(array(
+ 'public' => true,
+ 'page' => 1,
+ 'price' => 3.5,
+ 'title' => 'foo',
+ )),
+ ),
+ $route->getDefaults()
+ );
+ }
+
+ public function testMapInMapDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('map_in_map_defaults.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(
+ array(
+ '_controller' => 'AcmeBlogBundle:Blog:index',
+ 'values' => array('map' => array(
+ 'public' => true,
+ 'page' => 1,
+ 'price' => 3.5,
+ 'title' => 'foo',
+ )),
+ ),
+ $route->getDefaults()
+ );
+ }
+
+ public function testNullValuesInList()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('list_null_values.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(array(null, null, null, null, null, null), $route->getDefault('list'));
+ }
+
+ public function testNullValuesInMap()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('map_null_values.xml');
+ $route = $routeCollection->get('blog');
+
+ $this->assertSame(
+ array(
+ 'boolean' => null,
+ 'integer' => null,
+ 'float' => null,
+ 'string' => null,
+ 'list' => null,
+ 'map' => null,
+ ),
+ $route->getDefault('map')
+ );
+ }
+
+ public function testLoadRouteWithControllerAttribute()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $routeCollection = $loader->load('routing.xml');
+
+ $route = $routeCollection->get('app_homepage');
+
+ $this->assertSame('AppBundle:Homepage:show', $route->getDefault('_controller'));
+ }
+
+ public function testLoadRouteWithoutControllerAttribute()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $routeCollection = $loader->load('routing.xml');
+
+ $route = $routeCollection->get('app_logout');
+
+ $this->assertNull($route->getDefault('_controller'));
+ }
+
+ public function testLoadRouteWithControllerSetInDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $routeCollection = $loader->load('routing.xml');
+
+ $route = $routeCollection->get('app_blog');
+
+ $this->assertSame('AppBundle:Blog:list', $route->getDefault('_controller'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for "app_blog"/
+ */
+ public function testOverrideControllerInDefaults()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $loader->load('override_defaults.xml');
+ }
+
+ /**
+ * @dataProvider provideFilesImportingRoutesWithControllers
+ */
+ public function testImportRouteWithController($file)
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $routeCollection = $loader->load($file);
+
+ $route = $routeCollection->get('app_homepage');
+ $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));
+
+ $route = $routeCollection->get('app_blog');
+ $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));
+
+ $route = $routeCollection->get('app_logout');
+ $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));
+ }
+
+ public function provideFilesImportingRoutesWithControllers()
+ {
+ yield array('import_controller.xml');
+ yield array('import__controller.xml');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for the "import" tag/
+ */
+ public function testImportWithOverriddenController()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $loader->load('import_override_defaults.xml');
+ }
+
+ public function testImportRouteWithGlobMatchingSingleFile()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/glob')));
+ $routeCollection = $loader->load('import_single.xml');
+
+ $route = $routeCollection->get('bar_route');
+ $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller'));
+ }
+
+ public function testImportRouteWithGlobMatchingMultipleFiles()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/glob')));
+ $routeCollection = $loader->load('import_multiple.xml');
+
+ $route = $routeCollection->get('bar_route');
+ $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller'));
+
+ $route = $routeCollection->get('baz_route');
+ $this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller'));
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php b/assets/php/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php
new file mode 100644
index 0000000..822bddf
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php
@@ -0,0 +1,206 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Routing\Loader\YamlFileLoader;
+use Symfony\Component\Config\Resource\FileResource;
+
+class YamlFileLoaderTest extends TestCase
+{
+ public function testSupports()
+ {
+ $loader = new YamlFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock());
+
+ $this->assertTrue($loader->supports('foo.yml'), '->supports() returns true if the resource is loadable');
+ $this->assertTrue($loader->supports('foo.yaml'), '->supports() returns true if the resource is loadable');
+ $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable');
+
+ $this->assertTrue($loader->supports('foo.yml', 'yaml'), '->supports() checks the resource type if specified');
+ $this->assertTrue($loader->supports('foo.yaml', 'yaml'), '->supports() checks the resource type if specified');
+ $this->assertFalse($loader->supports('foo.yml', 'foo'), '->supports() checks the resource type if specified');
+ }
+
+ public function testLoadDoesNothingIfEmpty()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $collection = $loader->load('empty.yml');
+
+ $this->assertEquals(array(), $collection->all());
+ $this->assertEquals(array(new FileResource(realpath(__DIR__.'/../Fixtures/empty.yml'))), $collection->getResources());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @dataProvider getPathsToInvalidFiles
+ */
+ public function testLoadThrowsExceptionWithInvalidFile($filePath)
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $loader->load($filePath);
+ }
+
+ public function getPathsToInvalidFiles()
+ {
+ return array(
+ array('nonvalid.yml'),
+ array('nonvalid2.yml'),
+ array('incomplete.yml'),
+ array('nonvalidkeys.yml'),
+ array('nonesense_resource_plus_path.yml'),
+ array('nonesense_type_without_resource.yml'),
+ array('bad_format.yml'),
+ );
+ }
+
+ public function testLoadSpecialRouteName()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('special_route_name.yml');
+ $route = $routeCollection->get('#$péß^a|');
+
+ $this->assertInstanceOf('Symfony\Component\Routing\Route', $route);
+ $this->assertSame('/true', $route->getPath());
+ }
+
+ public function testLoadWithRoute()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('validpattern.yml');
+ $route = $routeCollection->get('blog_show');
+
+ $this->assertInstanceOf('Symfony\Component\Routing\Route', $route);
+ $this->assertSame('/blog/{slug}', $route->getPath());
+ $this->assertSame('{locale}.example.com', $route->getHost());
+ $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertSame('\w+', $route->getRequirement('locale'));
+ $this->assertSame('RouteCompiler', $route->getOption('compiler_class'));
+ $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods());
+ $this->assertEquals(array('https'), $route->getSchemes());
+ $this->assertEquals('context.getMethod() == "GET"', $route->getCondition());
+ }
+
+ public function testLoadWithResource()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('validresource.yml');
+ $routes = $routeCollection->all();
+
+ $this->assertCount(2, $routes, 'Two routes are loaded');
+ $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
+
+ foreach ($routes as $route) {
+ $this->assertSame('/{foo}/blog/{slug}', $route->getPath());
+ $this->assertSame('123', $route->getDefault('foo'));
+ $this->assertSame('\d+', $route->getRequirement('foo'));
+ $this->assertSame('bar', $route->getOption('foo'));
+ $this->assertSame('', $route->getHost());
+ $this->assertSame('context.getMethod() == "POST"', $route->getCondition());
+ }
+ }
+
+ public function testLoadRouteWithControllerAttribute()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $routeCollection = $loader->load('routing.yml');
+
+ $route = $routeCollection->get('app_homepage');
+
+ $this->assertSame('AppBundle:Homepage:show', $route->getDefault('_controller'));
+ }
+
+ public function testLoadRouteWithoutControllerAttribute()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $routeCollection = $loader->load('routing.yml');
+
+ $route = $routeCollection->get('app_logout');
+
+ $this->assertNull($route->getDefault('_controller'));
+ }
+
+ public function testLoadRouteWithControllerSetInDefaults()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $routeCollection = $loader->load('routing.yml');
+
+ $route = $routeCollection->get('app_blog');
+
+ $this->assertSame('AppBundle:Blog:list', $route->getDefault('_controller'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "app_blog"/
+ */
+ public function testOverrideControllerInDefaults()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $loader->load('override_defaults.yml');
+ }
+
+ /**
+ * @dataProvider provideFilesImportingRoutesWithControllers
+ */
+ public function testImportRouteWithController($file)
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $routeCollection = $loader->load($file);
+
+ $route = $routeCollection->get('app_homepage');
+ $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));
+
+ $route = $routeCollection->get('app_blog');
+ $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));
+
+ $route = $routeCollection->get('app_logout');
+ $this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));
+ }
+
+ public function provideFilesImportingRoutesWithControllers()
+ {
+ yield array('import_controller.yml');
+ yield array('import__controller.yml');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "_static"/
+ */
+ public function testImportWithOverriddenController()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
+ $loader->load('import_override_defaults.yml');
+ }
+
+ public function testImportRouteWithGlobMatchingSingleFile()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/glob')));
+ $routeCollection = $loader->load('import_single.yml');
+
+ $route = $routeCollection->get('bar_route');
+ $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller'));
+ }
+
+ public function testImportRouteWithGlobMatchingMultipleFiles()
+ {
+ $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/glob')));
+ $routeCollection = $loader->load('import_multiple.yml');
+
+ $route = $routeCollection->get('bar_route');
+ $this->assertSame('AppBundle:Bar:view', $route->getDefault('_controller'));
+
+ $route = $routeCollection->get('baz_route');
+ $this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller'));
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php
new file mode 100644
index 0000000..cfbb524
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher;
+
+use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
+use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
+use Symfony\Component\Routing\Matcher\UrlMatcher;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RequestContext;
+
+class DumpedRedirectableUrlMatcherTest extends RedirectableUrlMatcherTest
+{
+ protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
+ {
+ static $i = 0;
+
+ $class = 'DumpedRedirectableUrlMatcher'.++$i;
+ $dumper = new PhpMatcherDumper($routes);
+ eval('?>'.$dumper->dump(array('class' => $class, 'base_class' => 'Symfony\Component\Routing\Tests\Matcher\TestDumpedRedirectableUrlMatcher')));
+
+ return $this->getMockBuilder($class)
+ ->setConstructorArgs(array($context ?: new RequestContext()))
+ ->setMethods(array('redirect'))
+ ->getMock();
+ }
+}
+
+class TestDumpedRedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface
+{
+ public function redirect($path, $route, $scheme = null)
+ {
+ return array();
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php
new file mode 100644
index 0000000..880b2b1
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher;
+
+use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RequestContext;
+
+class DumpedUrlMatcherTest extends UrlMatcherTest
+{
+ /**
+ * @expectedException \LogicException
+ * @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.
+ */
+ public function testSchemeRequirement()
+ {
+ parent::testSchemeRequirement();
+ }
+
+ /**
+ * @expectedException \LogicException
+ * @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.
+ */
+ public function testSchemeAndMethodMismatch()
+ {
+ parent::testSchemeRequirement();
+ }
+
+ protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
+ {
+ static $i = 0;
+
+ $class = 'DumpedUrlMatcher'.++$i;
+ $dumper = new PhpMatcherDumper($routes);
+ eval('?>'.$dumper->dump(array('class' => $class)));
+
+ return new $class($context ?: new RequestContext());
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php
new file mode 100644
index 0000000..823efdb
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Matcher\Dumper\DumperCollection;
+
+class DumperCollectionTest extends TestCase
+{
+ public function testGetRoot()
+ {
+ $a = new DumperCollection();
+
+ $b = new DumperCollection();
+ $a->add($b);
+
+ $c = new DumperCollection();
+ $b->add($c);
+
+ $d = new DumperCollection();
+ $c->add($d);
+
+ $this->assertSame($a, $c->getRoot());
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
new file mode 100644
index 0000000..f29a6d6
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
@@ -0,0 +1,459 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
+use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
+use Symfony\Component\Routing\Matcher\UrlMatcher;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+class PhpMatcherDumperTest extends TestCase
+{
+ /**
+ * @var string
+ */
+ private $matcherClass;
+
+ /**
+ * @var string
+ */
+ private $dumpPath;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->matcherClass = uniqid('ProjectUrlMatcher');
+ $this->dumpPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_matcher.'.$this->matcherClass.'.php';
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ @unlink($this->dumpPath);
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testDumpWhenSchemeIsUsedWithoutAProperDumper()
+ {
+ $collection = new RouteCollection();
+ $collection->add('secure', new Route(
+ '/secure',
+ array(),
+ array(),
+ array(),
+ '',
+ array('https')
+ ));
+ $dumper = new PhpMatcherDumper($collection);
+ $dumper->dump();
+ }
+
+ public function testRedirectPreservesUrlEncoding()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo:bar/'));
+
+ $class = $this->generateDumpedMatcher($collection, true);
+
+ $matcher = $this->getMockBuilder($class)
+ ->setMethods(array('redirect'))
+ ->setConstructorArgs(array(new RequestContext()))
+ ->getMock();
+
+ $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/', 'foo')->willReturn(array());
+
+ $matcher->match('/foo%3Abar');
+ }
+
+ /**
+ * @dataProvider getRouteCollections
+ */
+ public function testDump(RouteCollection $collection, $fixture, $options = array())
+ {
+ $basePath = __DIR__.'/../../Fixtures/dumper/';
+
+ $dumper = new PhpMatcherDumper($collection);
+ $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.');
+ }
+
+ public function getRouteCollections()
+ {
+ /* test case 1 */
+
+ $collection = new RouteCollection();
+
+ $collection->add('overridden', new Route('/overridden'));
+
+ // defaults and requirements
+ $collection->add('foo', new Route(
+ '/foo/{bar}',
+ array('def' => 'test'),
+ array('bar' => 'baz|symfony')
+ ));
+ // method requirement
+ $collection->add('bar', new Route(
+ '/bar/{foo}',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('GET', 'head')
+ ));
+ // GET method requirement automatically adds HEAD as valid
+ $collection->add('barhead', new Route(
+ '/barhead/{foo}',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('GET')
+ ));
+ // simple
+ $collection->add('baz', new Route(
+ '/test/baz'
+ ));
+ // simple with extension
+ $collection->add('baz2', new Route(
+ '/test/baz.html'
+ ));
+ // trailing slash
+ $collection->add('baz3', new Route(
+ '/test/baz3/'
+ ));
+ // trailing slash with variable
+ $collection->add('baz4', new Route(
+ '/test/{foo}/'
+ ));
+ // trailing slash and method
+ $collection->add('baz5', new Route(
+ '/test/{foo}/',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('post')
+ ));
+ // complex name
+ $collection->add('baz.baz6', new Route(
+ '/test/{foo}/',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('put')
+ ));
+ // defaults without variable
+ $collection->add('foofoo', new Route(
+ '/foofoo',
+ array('def' => 'test')
+ ));
+ // pattern with quotes
+ $collection->add('quoter', new Route(
+ '/{quoter}',
+ array(),
+ array('quoter' => '[\']+')
+ ));
+ // space in pattern
+ $collection->add('space', new Route(
+ '/spa ce'
+ ));
+
+ // prefixes
+ $collection1 = new RouteCollection();
+ $collection1->add('overridden', new Route('/overridden1'));
+ $collection1->add('foo1', new Route('/{foo}'));
+ $collection1->add('bar1', new Route('/{bar}'));
+ $collection1->addPrefix('/b\'b');
+ $collection2 = new RouteCollection();
+ $collection2->addCollection($collection1);
+ $collection2->add('overridden', new Route('/{var}', array(), array('var' => '.*')));
+ $collection1 = new RouteCollection();
+ $collection1->add('foo2', new Route('/{foo1}'));
+ $collection1->add('bar2', new Route('/{bar1}'));
+ $collection1->addPrefix('/b\'b');
+ $collection2->addCollection($collection1);
+ $collection2->addPrefix('/a');
+ $collection->addCollection($collection2);
+
+ // overridden through addCollection() and multiple sub-collections with no own prefix
+ $collection1 = new RouteCollection();
+ $collection1->add('overridden2', new Route('/old'));
+ $collection1->add('helloWorld', new Route('/hello/{who}', array('who' => 'World!')));
+ $collection2 = new RouteCollection();
+ $collection3 = new RouteCollection();
+ $collection3->add('overridden2', new Route('/new'));
+ $collection3->add('hey', new Route('/hey/'));
+ $collection2->addCollection($collection3);
+ $collection1->addCollection($collection2);
+ $collection1->addPrefix('/multi');
+ $collection->addCollection($collection1);
+
+ // "dynamic" prefix
+ $collection1 = new RouteCollection();
+ $collection1->add('foo3', new Route('/{foo}'));
+ $collection1->add('bar3', new Route('/{bar}'));
+ $collection1->addPrefix('/b');
+ $collection1->addPrefix('{_locale}');
+ $collection->addCollection($collection1);
+
+ // route between collections
+ $collection->add('ababa', new Route('/ababa'));
+
+ // collection with static prefix but only one route
+ $collection1 = new RouteCollection();
+ $collection1->add('foo4', new Route('/{foo}'));
+ $collection1->addPrefix('/aba');
+ $collection->addCollection($collection1);
+
+ // prefix and host
+
+ $collection1 = new RouteCollection();
+
+ $route1 = new Route('/route1', array(), array(), array(), 'a.example.com');
+ $collection1->add('route1', $route1);
+
+ $route2 = new Route('/c2/route2', array(), array(), array(), 'a.example.com');
+ $collection1->add('route2', $route2);
+
+ $route3 = new Route('/c2/route3', array(), array(), array(), 'b.example.com');
+ $collection1->add('route3', $route3);
+
+ $route4 = new Route('/route4', array(), array(), array(), 'a.example.com');
+ $collection1->add('route4', $route4);
+
+ $route5 = new Route('/route5', array(), array(), array(), 'c.example.com');
+ $collection1->add('route5', $route5);
+
+ $route6 = new Route('/route6', array(), array(), array(), null);
+ $collection1->add('route6', $route6);
+
+ $collection->addCollection($collection1);
+
+ // host and variables
+
+ $collection1 = new RouteCollection();
+
+ $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com');
+ $collection1->add('route11', $route11);
+
+ $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com');
+ $collection1->add('route12', $route12);
+
+ $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com');
+ $collection1->add('route13', $route13);
+
+ $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com');
+ $collection1->add('route14', $route14);
+
+ $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com');
+ $collection1->add('route15', $route15);
+
+ $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null);
+ $collection1->add('route16', $route16);
+
+ $route17 = new Route('/route17', array(), array(), array(), null);
+ $collection1->add('route17', $route17);
+
+ $collection->addCollection($collection1);
+
+ // multiple sub-collections with a single route and a prefix each
+ $collection1 = new RouteCollection();
+ $collection1->add('a', new Route('/a...'));
+ $collection2 = new RouteCollection();
+ $collection2->add('b', new Route('/{var}'));
+ $collection3 = new RouteCollection();
+ $collection3->add('c', new Route('/{var}'));
+ $collection3->addPrefix('/c');
+ $collection2->addCollection($collection3);
+ $collection2->addPrefix('/b');
+ $collection1->addCollection($collection2);
+ $collection1->addPrefix('/a');
+ $collection->addCollection($collection1);
+
+ /* test case 2 */
+
+ $redirectCollection = clone $collection;
+
+ // force HTTPS redirection
+ $redirectCollection->add('secure', new Route(
+ '/secure',
+ array(),
+ array(),
+ array(),
+ '',
+ array('https')
+ ));
+
+ // force HTTP redirection
+ $redirectCollection->add('nonsecure', new Route(
+ '/nonsecure',
+ array(),
+ array(),
+ array(),
+ '',
+ array('http')
+ ));
+
+ /* test case 3 */
+
+ $rootprefixCollection = new RouteCollection();
+ $rootprefixCollection->add('static', new Route('/test'));
+ $rootprefixCollection->add('dynamic', new Route('/{var}'));
+ $rootprefixCollection->addPrefix('rootprefix');
+ $route = new Route('/with-condition');
+ $route->setCondition('context.getMethod() == "GET"');
+ $rootprefixCollection->add('with-condition', $route);
+
+ /* test case 4 */
+ $headMatchCasesCollection = new RouteCollection();
+ $headMatchCasesCollection->add('just_head', new Route(
+ '/just_head',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('HEAD')
+ ));
+ $headMatchCasesCollection->add('head_and_get', new Route(
+ '/head_and_get',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('HEAD', 'GET')
+ ));
+ $headMatchCasesCollection->add('get_and_head', new Route(
+ '/get_and_head',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('GET', 'HEAD')
+ ));
+ $headMatchCasesCollection->add('post_and_head', new Route(
+ '/post_and_head',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('POST', 'HEAD')
+ ));
+ $headMatchCasesCollection->add('put_and_post', new Route(
+ '/put_and_post',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('PUT', 'POST')
+ ));
+ $headMatchCasesCollection->add('put_and_get_and_head', new Route(
+ '/put_and_post',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('PUT', 'GET', 'HEAD')
+ ));
+
+ /* test case 5 */
+ $groupOptimisedCollection = new RouteCollection();
+ $groupOptimisedCollection->add('a_first', new Route('/a/11'));
+ $groupOptimisedCollection->add('a_second', new Route('/a/22'));
+ $groupOptimisedCollection->add('a_third', new Route('/a/333'));
+ $groupOptimisedCollection->add('a_wildcard', new Route('/{param}'));
+ $groupOptimisedCollection->add('a_fourth', new Route('/a/44/'));
+ $groupOptimisedCollection->add('a_fifth', new Route('/a/55/'));
+ $groupOptimisedCollection->add('a_sixth', new Route('/a/66/'));
+ $groupOptimisedCollection->add('nested_wildcard', new Route('/nested/{param}'));
+ $groupOptimisedCollection->add('nested_a', new Route('/nested/group/a/'));
+ $groupOptimisedCollection->add('nested_b', new Route('/nested/group/b/'));
+ $groupOptimisedCollection->add('nested_c', new Route('/nested/group/c/'));
+
+ $groupOptimisedCollection->add('slashed_a', new Route('/slashed/group/'));
+ $groupOptimisedCollection->add('slashed_b', new Route('/slashed/group/b/'));
+ $groupOptimisedCollection->add('slashed_c', new Route('/slashed/group/c/'));
+
+ $trailingSlashCollection = new RouteCollection();
+ $trailingSlashCollection->add('simple_trailing_slash_no_methods', new Route('/trailing/simple/no-methods/', array(), array(), array(), '', array(), array()));
+ $trailingSlashCollection->add('simple_trailing_slash_GET_method', new Route('/trailing/simple/get-method/', array(), array(), array(), '', array(), array('GET')));
+ $trailingSlashCollection->add('simple_trailing_slash_HEAD_method', new Route('/trailing/simple/head-method/', array(), array(), array(), '', array(), array('HEAD')));
+ $trailingSlashCollection->add('simple_trailing_slash_POST_method', new Route('/trailing/simple/post-method/', array(), array(), array(), '', array(), array('POST')));
+ $trailingSlashCollection->add('regex_trailing_slash_no_methods', new Route('/trailing/regex/no-methods/{param}/', array(), array(), array(), '', array(), array()));
+ $trailingSlashCollection->add('regex_trailing_slash_GET_method', new Route('/trailing/regex/get-method/{param}/', array(), array(), array(), '', array(), array('GET')));
+ $trailingSlashCollection->add('regex_trailing_slash_HEAD_method', new Route('/trailing/regex/head-method/{param}/', array(), array(), array(), '', array(), array('HEAD')));
+ $trailingSlashCollection->add('regex_trailing_slash_POST_method', new Route('/trailing/regex/post-method/{param}/', array(), array(), array(), '', array(), array('POST')));
+
+ $trailingSlashCollection->add('simple_not_trailing_slash_no_methods', new Route('/not-trailing/simple/no-methods', array(), array(), array(), '', array(), array()));
+ $trailingSlashCollection->add('simple_not_trailing_slash_GET_method', new Route('/not-trailing/simple/get-method', array(), array(), array(), '', array(), array('GET')));
+ $trailingSlashCollection->add('simple_not_trailing_slash_HEAD_method', new Route('/not-trailing/simple/head-method', array(), array(), array(), '', array(), array('HEAD')));
+ $trailingSlashCollection->add('simple_not_trailing_slash_POST_method', new Route('/not-trailing/simple/post-method', array(), array(), array(), '', array(), array('POST')));
+ $trailingSlashCollection->add('regex_not_trailing_slash_no_methods', new Route('/not-trailing/regex/no-methods/{param}', array(), array(), array(), '', array(), array()));
+ $trailingSlashCollection->add('regex_not_trailing_slash_GET_method', new Route('/not-trailing/regex/get-method/{param}', array(), array(), array(), '', array(), array('GET')));
+ $trailingSlashCollection->add('regex_not_trailing_slash_HEAD_method', new Route('/not-trailing/regex/head-method/{param}', array(), array(), array(), '', array(), array('HEAD')));
+ $trailingSlashCollection->add('regex_not_trailing_slash_POST_method', new Route('/not-trailing/regex/post-method/{param}', array(), array(), array(), '', array(), array('POST')));
+
+ return array(
+ array(new RouteCollection(), 'url_matcher0.php', array()),
+ array($collection, 'url_matcher1.php', array()),
+ array($redirectCollection, 'url_matcher2.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')),
+ array($rootprefixCollection, 'url_matcher3.php', array()),
+ array($headMatchCasesCollection, 'url_matcher4.php', array()),
+ array($groupOptimisedCollection, 'url_matcher5.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')),
+ array($trailingSlashCollection, 'url_matcher6.php', array()),
+ array($trailingSlashCollection, 'url_matcher7.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')),
+ );
+ }
+
+ /**
+ * @param $dumper
+ */
+ private function generateDumpedMatcher(RouteCollection $collection, $redirectableStub = false)
+ {
+ $options = array('class' => $this->matcherClass);
+
+ if ($redirectableStub) {
+ $options['base_class'] = '\Symfony\Component\Routing\Tests\Matcher\Dumper\RedirectableUrlMatcherStub';
+ }
+
+ $dumper = new PhpMatcherDumper($collection);
+ $code = $dumper->dump($options);
+
+ file_put_contents($this->dumpPath, $code);
+ include $this->dumpPath;
+
+ return $this->matcherClass;
+ }
+}
+
+abstract class RedirectableUrlMatcherStub extends UrlMatcher implements RedirectableUrlMatcherInterface
+{
+ public function redirect($path, $route, $scheme = null)
+ {
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php
new file mode 100644
index 0000000..37419e7
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php
@@ -0,0 +1,175 @@
+compile()->getStaticPrefix();
+ $collection->addRoute($staticPrefix, $name);
+ }
+
+ $collection->optimizeGroups();
+ $dumped = $this->dumpCollection($collection);
+ $this->assertEquals($expected, $dumped);
+ }
+
+ public function routeProvider()
+ {
+ return array(
+ 'Simple - not nested' => array(
+ array(
+ array('/', 'root'),
+ array('/prefix/segment/', 'prefix_segment'),
+ array('/leading/segment/', 'leading_segment'),
+ ),
+ << array(
+ array(
+ array('/', 'root'),
+ array('/prefix/segment/aa', 'prefix_segment'),
+ array('/prefix/segment/bb', 'leading_segment'),
+ ),
+ << array(
+ array(
+ array('/', 'root'),
+ array('/prefix/segment/', 'prefix_segment'),
+ array('/prefix/segment/bb', 'leading_segment'),
+ ),
+ << /prefix/segment prefix_segment
+-> /prefix/segment/bb leading_segment
+EOF
+ ),
+ 'Simple one level nesting' => array(
+ array(
+ array('/', 'root'),
+ array('/group/segment/', 'nested_segment'),
+ array('/group/thing/', 'some_segment'),
+ array('/group/other/', 'other_segment'),
+ ),
+ << /group/segment nested_segment
+-> /group/thing some_segment
+-> /group/other other_segment
+EOF
+ ),
+ 'Retain matching order with groups' => array(
+ array(
+ array('/group/aa/', 'aa'),
+ array('/group/bb/', 'bb'),
+ array('/group/cc/', 'cc'),
+ array('/', 'root'),
+ array('/group/dd/', 'dd'),
+ array('/group/ee/', 'ee'),
+ array('/group/ff/', 'ff'),
+ ),
+ << /group/aa aa
+-> /group/bb bb
+-> /group/cc cc
+/ root
+/group
+-> /group/dd dd
+-> /group/ee ee
+-> /group/ff ff
+EOF
+ ),
+ 'Retain complex matching order with groups at base' => array(
+ array(
+ array('/aaa/111/', 'first_aaa'),
+ array('/prefixed/group/aa/', 'aa'),
+ array('/prefixed/group/bb/', 'bb'),
+ array('/prefixed/group/cc/', 'cc'),
+ array('/prefixed/', 'root'),
+ array('/prefixed/group/dd/', 'dd'),
+ array('/prefixed/group/ee/', 'ee'),
+ array('/prefixed/group/ff/', 'ff'),
+ array('/aaa/222/', 'second_aaa'),
+ array('/aaa/333/', 'third_aaa'),
+ ),
+ << /aaa/111 first_aaa
+-> /aaa/222 second_aaa
+-> /aaa/333 third_aaa
+/prefixed
+-> /prefixed/group
+-> -> /prefixed/group/aa aa
+-> -> /prefixed/group/bb bb
+-> -> /prefixed/group/cc cc
+-> /prefixed root
+-> /prefixed/group
+-> -> /prefixed/group/dd dd
+-> -> /prefixed/group/ee ee
+-> -> /prefixed/group/ff ff
+EOF
+ ),
+
+ 'Group regardless of segments' => array(
+ array(
+ array('/aaa-111/', 'a1'),
+ array('/aaa-222/', 'a2'),
+ array('/aaa-333/', 'a3'),
+ array('/group-aa/', 'g1'),
+ array('/group-bb/', 'g2'),
+ array('/group-cc/', 'g3'),
+ ),
+ << /aaa-111 a1
+-> /aaa-222 a2
+-> /aaa-333 a3
+/group-
+-> /group-aa g1
+-> /group-bb g2
+-> /group-cc g3
+EOF
+ ),
+ );
+ }
+
+ private function dumpCollection(StaticPrefixCollection $collection, $prefix = '')
+ {
+ $lines = array();
+
+ foreach ($collection->getItems() as $item) {
+ if ($item instanceof StaticPrefixCollection) {
+ $lines[] = $prefix.$item->getPrefix();
+ $lines[] = $this->dumpCollection($item, $prefix.'-> ');
+ } else {
+ $lines[] = $prefix.implode(' ', $item);
+ }
+ }
+
+ return implode("\n", $lines);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php
new file mode 100644
index 0000000..7984391
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php
@@ -0,0 +1,124 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RequestContext;
+
+class RedirectableUrlMatcherTest extends UrlMatcherTest
+{
+ public function testMissingTrailingSlash()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/'));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher->expects($this->once())->method('redirect')->will($this->returnValue(array()));
+ $matcher->match('/foo');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testRedirectWhenNoSlashForNonSafeMethod()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/'));
+
+ $context = new RequestContext();
+ $context->setMethod('POST');
+ $matcher = $this->getUrlMatcher($coll, $context);
+ $matcher->match('/foo');
+ }
+
+ public function testSchemeRedirectRedirectsToFirstScheme()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('FTP', 'HTTPS')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher
+ ->expects($this->once())
+ ->method('redirect')
+ ->with('/foo', 'foo', 'ftp')
+ ->will($this->returnValue(array('_route' => 'foo')))
+ ;
+ $matcher->match('/foo');
+ }
+
+ public function testNoSchemaRedirectIfOneOfMultipleSchemesMatches()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https', 'http')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher
+ ->expects($this->never())
+ ->method('redirect');
+ $matcher->match('/foo');
+ }
+
+ public function testSchemeRedirectWithParams()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{bar}', array(), array(), array(), '', array('https')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher
+ ->expects($this->once())
+ ->method('redirect')
+ ->with('/foo/baz', 'foo', 'https')
+ ->will($this->returnValue(array('redirect' => 'value')))
+ ;
+ $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'redirect' => 'value'), $matcher->match('/foo/baz'));
+ }
+
+ public function testSlashRedirectWithParams()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{bar}/'));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher
+ ->expects($this->once())
+ ->method('redirect')
+ ->with('/foo/baz/', 'foo', null)
+ ->will($this->returnValue(array('redirect' => 'value')))
+ ;
+ $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'redirect' => 'value'), $matcher->match('/foo/baz'));
+ }
+
+ public function testRedirectPreservesUrlEncoding()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo:bar/'));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/')->willReturn(array());
+ $matcher->match('/foo%3Abar');
+ }
+
+ public function testSchemeRequirement()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https')));
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
+ $matcher->expects($this->once())->method('redirect')->with('/foo', 'foo', 'https')->willReturn(array());
+ $this->assertSame(array('_route' => 'foo'), $matcher->match('/foo'));
+ }
+
+ protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
+ {
+ return $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($routes, $context ?: new RequestContext()));
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php
new file mode 100644
index 0000000..9f0529e
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Matcher\TraceableUrlMatcher;
+
+class TraceableUrlMatcherTest extends TestCase
+{
+ public function test()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('POST')));
+ $coll->add('bar', new Route('/bar/{id}', array(), array('id' => '\d+')));
+ $coll->add('bar1', new Route('/bar/{name}', array(), array('id' => '\w+'), array(), '', array(), array('POST')));
+ $coll->add('bar2', new Route('/foo', array(), array(), array(), 'baz'));
+ $coll->add('bar3', new Route('/foo1', array(), array(), array(), 'baz'));
+ $coll->add('bar4', new Route('/foo2', array(), array(), array(), 'baz', array(), array(), 'context.getMethod() == "GET"'));
+
+ $context = new RequestContext();
+ $context->setHost('baz');
+
+ $matcher = new TraceableUrlMatcher($coll, $context);
+ $traces = $matcher->getTraces('/babar');
+ $this->assertSame(array(0, 0, 0, 0, 0, 0), $this->getLevels($traces));
+
+ $traces = $matcher->getTraces('/foo');
+ $this->assertSame(array(1, 0, 0, 2), $this->getLevels($traces));
+
+ $traces = $matcher->getTraces('/bar/12');
+ $this->assertSame(array(0, 2), $this->getLevels($traces));
+
+ $traces = $matcher->getTraces('/bar/dd');
+ $this->assertSame(array(0, 1, 1, 0, 0, 0), $this->getLevels($traces));
+
+ $traces = $matcher->getTraces('/foo1');
+ $this->assertSame(array(0, 0, 0, 0, 2), $this->getLevels($traces));
+
+ $context->setMethod('POST');
+ $traces = $matcher->getTraces('/foo');
+ $this->assertSame(array(2), $this->getLevels($traces));
+
+ $traces = $matcher->getTraces('/bar/dd');
+ $this->assertSame(array(0, 1, 2), $this->getLevels($traces));
+
+ $traces = $matcher->getTraces('/foo2');
+ $this->assertSame(array(0, 0, 0, 0, 0, 1), $this->getLevels($traces));
+ }
+
+ public function testMatchRouteOnMultipleHosts()
+ {
+ $routes = new RouteCollection();
+ $routes->add('first', new Route(
+ '/mypath/',
+ array('_controller' => 'MainBundle:Info:first'),
+ array(),
+ array(),
+ 'some.example.com'
+ ));
+
+ $routes->add('second', new Route(
+ '/mypath/',
+ array('_controller' => 'MainBundle:Info:second'),
+ array(),
+ array(),
+ 'another.example.com'
+ ));
+
+ $context = new RequestContext();
+ $context->setHost('baz');
+
+ $matcher = new TraceableUrlMatcher($routes, $context);
+
+ $traces = $matcher->getTraces('/mypath/');
+ $this->assertSame(
+ array(TraceableUrlMatcher::ROUTE_ALMOST_MATCHES, TraceableUrlMatcher::ROUTE_ALMOST_MATCHES),
+ $this->getLevels($traces)
+ );
+ }
+
+ public function getLevels($traces)
+ {
+ $levels = array();
+ foreach ($traces as $trace) {
+ $levels[] = $trace['level'];
+ }
+
+ return $levels;
+ }
+
+ public function testRoutesWithConditions()
+ {
+ $routes = new RouteCollection();
+ $routes->add('foo', new Route('/foo', array(), array(), array(), 'baz', array(), array(), "request.headers.get('User-Agent') matches '/firefox/i'"));
+
+ $context = new RequestContext();
+ $context->setHost('baz');
+
+ $matcher = new TraceableUrlMatcher($routes, $context);
+
+ $notMatchingRequest = Request::create('/foo', 'GET');
+ $traces = $matcher->getTracesForRequest($notMatchingRequest);
+ $this->assertEquals("Condition \"request.headers.get('User-Agent') matches '/firefox/i'\" does not evaluate to \"true\"", $traces[0]['log']);
+
+ $matchingRequest = Request::create('/foo', 'GET', array(), array(), array(), array('HTTP_USER_AGENT' => 'Firefox'));
+ $traces = $matcher->getTracesForRequest($matchingRequest);
+ $this->assertEquals('Route matches!', $traces[0]['log']);
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php b/assets/php/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php
new file mode 100644
index 0000000..e8d31e2
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php
@@ -0,0 +1,509 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Matcher\UrlMatcher;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RequestContext;
+
+class UrlMatcherTest extends TestCase
+{
+ public function testNoMethodSoAllowed()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo'));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertInternalType('array', $matcher->match('/foo'));
+ }
+
+ public function testMethodNotAllowed()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('post')));
+
+ $matcher = $this->getUrlMatcher($coll);
+
+ try {
+ $matcher->match('/foo');
+ $this->fail();
+ } catch (MethodNotAllowedException $e) {
+ $this->assertEquals(array('POST'), $e->getAllowedMethods());
+ }
+ }
+
+ public function testMethodNotAllowedOnRoot()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/', array(), array(), array(), '', array(), array('GET')));
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'POST'));
+
+ try {
+ $matcher->match('/');
+ $this->fail();
+ } catch (MethodNotAllowedException $e) {
+ $this->assertEquals(array('GET'), $e->getAllowedMethods());
+ }
+ }
+
+ public function testHeadAllowedWhenRequirementContainsGet()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get')));
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'head'));
+ $this->assertInternalType('array', $matcher->match('/foo'));
+ }
+
+ public function testMethodNotAllowedAggregatesAllowedMethods()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo1', new Route('/foo', array(), array(), array(), '', array(), array('post')));
+ $coll->add('foo2', new Route('/foo', array(), array(), array(), '', array(), array('put', 'delete')));
+
+ $matcher = $this->getUrlMatcher($coll);
+
+ try {
+ $matcher->match('/foo');
+ $this->fail();
+ } catch (MethodNotAllowedException $e) {
+ $this->assertEquals(array('POST', 'PUT', 'DELETE'), $e->getAllowedMethods());
+ }
+ }
+
+ public function testMatch()
+ {
+ // test the patterns are matched and parameters are returned
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo/{bar}'));
+ $matcher = $this->getUrlMatcher($collection);
+ try {
+ $matcher->match('/no-match');
+ $this->fail();
+ } catch (ResourceNotFoundException $e) {
+ }
+ $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz'), $matcher->match('/foo/baz'));
+
+ // test that defaults are merged
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo/{bar}', array('def' => 'test')));
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'def' => 'test'), $matcher->match('/foo/baz'));
+
+ // test that route "method" is ignored if no method is given in the context
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get', 'head')));
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertInternalType('array', $matcher->match('/foo'));
+
+ // route does not match with POST method context
+ $matcher = $this->getUrlMatcher($collection, new RequestContext('', 'post'));
+ try {
+ $matcher->match('/foo');
+ $this->fail();
+ } catch (MethodNotAllowedException $e) {
+ }
+
+ // route does match with GET or HEAD method context
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertInternalType('array', $matcher->match('/foo'));
+ $matcher = $this->getUrlMatcher($collection, new RequestContext('', 'head'));
+ $this->assertInternalType('array', $matcher->match('/foo'));
+
+ // route with an optional variable as the first segment
+ $collection = new RouteCollection();
+ $collection->add('bar', new Route('/{bar}/foo', array('bar' => 'bar'), array('bar' => 'foo|bar')));
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/bar/foo'));
+ $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo/foo'));
+
+ $collection = new RouteCollection();
+ $collection->add('bar', new Route('/{bar}', array('bar' => 'bar'), array('bar' => 'foo|bar')));
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo'));
+ $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/'));
+
+ // route with only optional variables
+ $collection = new RouteCollection();
+ $collection->add('bar', new Route('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar'), array()));
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'bar' => 'bar'), $matcher->match('/'));
+ $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'bar'), $matcher->match('/a'));
+ $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'b'), $matcher->match('/a/b'));
+ }
+
+ public function testMatchWithPrefixes()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/{foo}'));
+ $collection->addPrefix('/b');
+ $collection->addPrefix('/a');
+
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_route' => 'foo', 'foo' => 'foo'), $matcher->match('/a/b/foo'));
+ }
+
+ public function testMatchWithDynamicPrefix()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/{foo}'));
+ $collection->addPrefix('/b');
+ $collection->addPrefix('/{_locale}');
+
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_locale' => 'fr', '_route' => 'foo', 'foo' => 'foo'), $matcher->match('/fr/b/foo'));
+ }
+
+ public function testMatchSpecialRouteName()
+ {
+ $collection = new RouteCollection();
+ $collection->add('$péß^a|', new Route('/bar'));
+
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_route' => '$péß^a|'), $matcher->match('/bar'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testTrailingEncodedNewlineIsNotOverlooked()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo'));
+
+ $matcher = $this->getUrlMatcher($collection);
+ $matcher->match('/foo%0a');
+ }
+
+ public function testMatchNonAlpha()
+ {
+ $collection = new RouteCollection();
+ $chars = '!"$%éà &\'()*+,./:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[]^_`abcdefghijklmnopqrstuvwxyz{|}~-';
+ $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '['.preg_quote($chars).']+'), array('utf8' => true)));
+
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.rawurlencode($chars).'/bar'));
+ $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.strtr($chars, array('%' => '%25')).'/bar'));
+ }
+
+ public function testMatchWithDotMetacharacterInRequirements()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '.+')));
+
+ $matcher = $this->getUrlMatcher($collection);
+ $this->assertEquals(array('_route' => 'foo', 'foo' => "\n"), $matcher->match('/'.urlencode("\n").'/bar'), 'linefeed character is matched');
+ }
+
+ public function testMatchOverriddenRoute()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo'));
+
+ $collection1 = new RouteCollection();
+ $collection1->add('foo', new Route('/foo1'));
+
+ $collection->addCollection($collection1);
+
+ $matcher = $this->getUrlMatcher($collection);
+
+ $this->assertEquals(array('_route' => 'foo'), $matcher->match('/foo1'));
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException');
+ $this->assertEquals(array(), $matcher->match('/foo'));
+ }
+
+ public function testMatchRegression()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{foo}'));
+ $coll->add('bar', new Route('/foo/bar/{foo}'));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertEquals(array('foo' => 'bar', '_route' => 'bar'), $matcher->match('/foo/bar/bar'));
+
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/{bar}'));
+ $matcher = $this->getUrlMatcher($collection);
+ try {
+ $matcher->match('/');
+ $this->fail();
+ } catch (ResourceNotFoundException $e) {
+ }
+ }
+
+ public function testDefaultRequirementForOptionalVariables()
+ {
+ $coll = new RouteCollection();
+ $coll->add('test', new Route('/{page}.{_format}', array('page' => 'index', '_format' => 'html')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertEquals(array('page' => 'my-page', '_format' => 'xml', '_route' => 'test'), $matcher->match('/my-page.xml'));
+ }
+
+ public function testMatchingIsEager()
+ {
+ $coll = new RouteCollection();
+ $coll->add('test', new Route('/{foo}-{bar}-', array(), array('foo' => '.+', 'bar' => '.+')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertEquals(array('foo' => 'text1-text2-text3', 'bar' => 'text4', '_route' => 'test'), $matcher->match('/text1-text2-text3-text4-'));
+ }
+
+ public function testAdjacentVariables()
+ {
+ $coll = new RouteCollection();
+ $coll->add('test', new Route('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => 'y|Y')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ // 'w' eagerly matches as much as possible and the other variables match the remaining chars.
+ // This also shows that the variables w-z must all exclude the separating char (the dot '.' in this case) by default requirement.
+ // Otherwise they would also consume '.xml' and _format would never match as it's an optional variable.
+ $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'Y', 'z' => 'Z', '_format' => 'xml', '_route' => 'test'), $matcher->match('/wwwwwxYZ.xml'));
+ // As 'y' has custom requirement and can only be of value 'y|Y', it will leave 'ZZZ' to variable z.
+ // So with carefully chosen requirements adjacent variables, can be useful.
+ $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'ZZZ', '_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxyZZZ'));
+ // z and _format are optional.
+ $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'default-z', '_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxy'));
+
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException');
+ $matcher->match('/wxy.html');
+ }
+
+ public function testOptionalVariableWithNoRealSeparator()
+ {
+ $coll = new RouteCollection();
+ $coll->add('test', new Route('/get{what}', array('what' => 'All')));
+ $matcher = $this->getUrlMatcher($coll);
+
+ $this->assertEquals(array('what' => 'All', '_route' => 'test'), $matcher->match('/get'));
+ $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSites'));
+
+ // Usually the character in front of an optional parameter can be left out, e.g. with pattern '/get/{what}' just '/get' would match.
+ // But here the 't' in 'get' is not a separating character, so it makes no sense to match without it.
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException');
+ $matcher->match('/ge');
+ }
+
+ public function testRequiredVariableWithNoRealSeparator()
+ {
+ $coll = new RouteCollection();
+ $coll->add('test', new Route('/get{what}Suffix'));
+ $matcher = $this->getUrlMatcher($coll);
+
+ $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSitesSuffix'));
+ }
+
+ public function testDefaultRequirementOfVariable()
+ {
+ $coll = new RouteCollection();
+ $coll->add('test', new Route('/{page}.{_format}'));
+ $matcher = $this->getUrlMatcher($coll);
+
+ $this->assertEquals(array('page' => 'index', '_format' => 'mobile.html', '_route' => 'test'), $matcher->match('/index.mobile.html'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testDefaultRequirementOfVariableDisallowsSlash()
+ {
+ $coll = new RouteCollection();
+ $coll->add('test', new Route('/{page}.{_format}'));
+ $matcher = $this->getUrlMatcher($coll);
+
+ $matcher->match('/index.sl/ash');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testDefaultRequirementOfVariableDisallowsNextSeparator()
+ {
+ $coll = new RouteCollection();
+ $coll->add('test', new Route('/{page}.{_format}', array(), array('_format' => 'html|xml')));
+ $matcher = $this->getUrlMatcher($coll);
+
+ $matcher->match('/do.t.html');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testSchemeRequirement()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https')));
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher->match('/foo');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testCondition()
+ {
+ $coll = new RouteCollection();
+ $route = new Route('/foo');
+ $route->setCondition('context.getMethod() == "POST"');
+ $coll->add('foo', $route);
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher->match('/foo');
+ }
+
+ public function testRequestCondition()
+ {
+ $coll = new RouteCollection();
+ $route = new Route('/foo/{bar}');
+ $route->setCondition('request.getBaseUrl() == "/sub/front.php" and request.getPathInfo() == "/foo/bar"');
+ $coll->add('foo', $route);
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('/sub/front.php'));
+ $this->assertEquals(array('bar' => 'bar', '_route' => 'foo'), $matcher->match('/foo/bar'));
+ }
+
+ public function testDecodeOnce()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{foo}'));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523'));
+ }
+
+ public function testCannotRelyOnPrefix()
+ {
+ $coll = new RouteCollection();
+
+ $subColl = new RouteCollection();
+ $subColl->add('bar', new Route('/bar'));
+ $subColl->addPrefix('/prefix');
+ // overwrite the pattern, so the prefix is not valid anymore for this route in the collection
+ $subColl->get('bar')->setPath('/new');
+
+ $coll->addCollection($subColl);
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertEquals(array('_route' => 'bar'), $matcher->match('/new'));
+ }
+
+ public function testWithHost()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com'));
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar'));
+ }
+
+ public function testWithHostOnRouteCollection()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{foo}'));
+ $coll->add('bar', new Route('/bar/{foo}', array(), array(), array(), '{locale}.example.net'));
+ $coll->setHost('{locale}.example.com');
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar'));
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testWithOutHostHostDoesNotMatch()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com'));
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'example.com'));
+ $matcher->match('/foo/bar');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testPathIsCaseSensitive()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/locale', array(), array('locale' => 'EN|FR|DE')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher->match('/en');
+ }
+
+ public function testHostIsCaseInsensitive()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/', array(), array('locale' => 'EN|FR|DE'), array(), '{locale}.example.com'));
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $this->assertEquals(array('_route' => 'foo', 'locale' => 'en'), $matcher->match('/'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\NoConfigurationException
+ */
+ public function testNoConfiguration()
+ {
+ $coll = new RouteCollection();
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher->match('/');
+ }
+
+ public function testNestedCollections()
+ {
+ $coll = new RouteCollection();
+
+ $subColl = new RouteCollection();
+ $subColl->add('a', new Route('/a'));
+ $subColl->add('b', new Route('/b'));
+ $subColl->add('c', new Route('/c'));
+ $subColl->addPrefix('/p');
+ $coll->addCollection($subColl);
+
+ $coll->add('baz', new Route('/{baz}'));
+
+ $subColl = new RouteCollection();
+ $subColl->add('buz', new Route('/buz'));
+ $subColl->addPrefix('/prefix');
+ $coll->addCollection($subColl);
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertEquals(array('_route' => 'a'), $matcher->match('/p/a'));
+ $this->assertEquals(array('_route' => 'baz', 'baz' => 'p'), $matcher->match('/p'));
+ $this->assertEquals(array('_route' => 'buz'), $matcher->match('/prefix/buz'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
+ */
+ public function testSchemeAndMethodMismatch()
+ {
+ $coll = new RouteCollection();
+ $coll->add('foo', new Route('/', array(), array(), array(), null, array('https'), array('POST')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $matcher->match('/');
+ }
+
+ protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
+ {
+ return new UrlMatcher($routes, $context ?: new RequestContext());
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/RequestContextTest.php b/assets/php/vendor/symfony/routing/Tests/RequestContextTest.php
new file mode 100644
index 0000000..ffe29d1
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/RequestContextTest.php
@@ -0,0 +1,160 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\RequestContext;
+
+class RequestContextTest extends TestCase
+{
+ public function testConstruct()
+ {
+ $requestContext = new RequestContext(
+ 'foo',
+ 'post',
+ 'foo.bar',
+ 'HTTPS',
+ 8080,
+ 444,
+ '/baz',
+ 'bar=foobar'
+ );
+
+ $this->assertEquals('foo', $requestContext->getBaseUrl());
+ $this->assertEquals('POST', $requestContext->getMethod());
+ $this->assertEquals('foo.bar', $requestContext->getHost());
+ $this->assertEquals('https', $requestContext->getScheme());
+ $this->assertSame(8080, $requestContext->getHttpPort());
+ $this->assertSame(444, $requestContext->getHttpsPort());
+ $this->assertEquals('/baz', $requestContext->getPathInfo());
+ $this->assertEquals('bar=foobar', $requestContext->getQueryString());
+ }
+
+ public function testFromRequest()
+ {
+ $request = Request::create('https://test.com:444/foo?bar=baz');
+ $requestContext = new RequestContext();
+ $requestContext->setHttpPort(123);
+ $requestContext->fromRequest($request);
+
+ $this->assertEquals('', $requestContext->getBaseUrl());
+ $this->assertEquals('GET', $requestContext->getMethod());
+ $this->assertEquals('test.com', $requestContext->getHost());
+ $this->assertEquals('https', $requestContext->getScheme());
+ $this->assertEquals('/foo', $requestContext->getPathInfo());
+ $this->assertEquals('bar=baz', $requestContext->getQueryString());
+ $this->assertSame(123, $requestContext->getHttpPort());
+ $this->assertSame(444, $requestContext->getHttpsPort());
+
+ $request = Request::create('http://test.com:8080/foo?bar=baz');
+ $requestContext = new RequestContext();
+ $requestContext->setHttpsPort(567);
+ $requestContext->fromRequest($request);
+
+ $this->assertSame(8080, $requestContext->getHttpPort());
+ $this->assertSame(567, $requestContext->getHttpsPort());
+ }
+
+ public function testGetParameters()
+ {
+ $requestContext = new RequestContext();
+ $this->assertEquals(array(), $requestContext->getParameters());
+
+ $requestContext->setParameters(array('foo' => 'bar'));
+ $this->assertEquals(array('foo' => 'bar'), $requestContext->getParameters());
+ }
+
+ public function testHasParameter()
+ {
+ $requestContext = new RequestContext();
+ $requestContext->setParameters(array('foo' => 'bar'));
+
+ $this->assertTrue($requestContext->hasParameter('foo'));
+ $this->assertFalse($requestContext->hasParameter('baz'));
+ }
+
+ public function testGetParameter()
+ {
+ $requestContext = new RequestContext();
+ $requestContext->setParameters(array('foo' => 'bar'));
+
+ $this->assertEquals('bar', $requestContext->getParameter('foo'));
+ $this->assertNull($requestContext->getParameter('baz'));
+ }
+
+ public function testSetParameter()
+ {
+ $requestContext = new RequestContext();
+ $requestContext->setParameter('foo', 'bar');
+
+ $this->assertEquals('bar', $requestContext->getParameter('foo'));
+ }
+
+ public function testMethod()
+ {
+ $requestContext = new RequestContext();
+ $requestContext->setMethod('post');
+
+ $this->assertSame('POST', $requestContext->getMethod());
+ }
+
+ public function testScheme()
+ {
+ $requestContext = new RequestContext();
+ $requestContext->setScheme('HTTPS');
+
+ $this->assertSame('https', $requestContext->getScheme());
+ }
+
+ public function testHost()
+ {
+ $requestContext = new RequestContext();
+ $requestContext->setHost('eXampLe.com');
+
+ $this->assertSame('example.com', $requestContext->getHost());
+ }
+
+ public function testQueryString()
+ {
+ $requestContext = new RequestContext();
+ $requestContext->setQueryString(null);
+
+ $this->assertSame('', $requestContext->getQueryString());
+ }
+
+ public function testPort()
+ {
+ $requestContext = new RequestContext();
+ $requestContext->setHttpPort('123');
+ $requestContext->setHttpsPort('456');
+
+ $this->assertSame(123, $requestContext->getHttpPort());
+ $this->assertSame(456, $requestContext->getHttpsPort());
+ }
+
+ public function testFluentInterface()
+ {
+ $requestContext = new RequestContext();
+
+ $this->assertSame($requestContext, $requestContext->setBaseUrl('/app.php'));
+ $this->assertSame($requestContext, $requestContext->setPathInfo('/index'));
+ $this->assertSame($requestContext, $requestContext->setMethod('POST'));
+ $this->assertSame($requestContext, $requestContext->setScheme('https'));
+ $this->assertSame($requestContext, $requestContext->setHost('example.com'));
+ $this->assertSame($requestContext, $requestContext->setQueryString('foo=bar'));
+ $this->assertSame($requestContext, $requestContext->setHttpPort(80));
+ $this->assertSame($requestContext, $requestContext->setHttpsPort(443));
+ $this->assertSame($requestContext, $requestContext->setParameters(array()));
+ $this->assertSame($requestContext, $requestContext->setParameter('foo', 'bar'));
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php b/assets/php/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php
new file mode 100644
index 0000000..76a042d
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php
@@ -0,0 +1,364 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Routing\Loader\YamlFileLoader;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RouteCollectionBuilder;
+
+class RouteCollectionBuilderTest extends TestCase
+{
+ public function testImport()
+ {
+ $resolvedLoader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock();
+ $resolver->expects($this->once())
+ ->method('resolve')
+ ->with('admin_routing.yml', 'yaml')
+ ->will($this->returnValue($resolvedLoader));
+
+ $originalRoute = new Route('/foo/path');
+ $expectedCollection = new RouteCollection();
+ $expectedCollection->add('one_test_route', $originalRoute);
+ $expectedCollection->addResource(new FileResource(__DIR__.'/Fixtures/file_resource.yml'));
+
+ $resolvedLoader
+ ->expects($this->once())
+ ->method('load')
+ ->with('admin_routing.yml', 'yaml')
+ ->will($this->returnValue($expectedCollection));
+
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $loader->expects($this->any())
+ ->method('getResolver')
+ ->will($this->returnValue($resolver));
+
+ // import the file!
+ $routes = new RouteCollectionBuilder($loader);
+ $importedRoutes = $routes->import('admin_routing.yml', '/', 'yaml');
+
+ // we should get back a RouteCollectionBuilder
+ $this->assertInstanceOf('Symfony\Component\Routing\RouteCollectionBuilder', $importedRoutes);
+
+ // get the collection back so we can look at it
+ $addedCollection = $importedRoutes->build();
+ $route = $addedCollection->get('one_test_route');
+ $this->assertSame($originalRoute, $route);
+ // should return file_resource.yml, which is in the original collection
+ $this->assertCount(1, $addedCollection->getResources());
+
+ // make sure the routes were imported into the top-level builder
+ $routeCollection = $routes->build();
+ $this->assertCount(1, $routes->build());
+ $this->assertCount(1, $routeCollection->getResources());
+ }
+
+ public function testImportAddResources()
+ {
+ $routeCollectionBuilder = new RouteCollectionBuilder(new YamlFileLoader(new FileLocator(array(__DIR__.'/Fixtures/'))));
+ $routeCollectionBuilder->import('file_resource.yml');
+ $routeCollection = $routeCollectionBuilder->build();
+
+ $this->assertCount(1, $routeCollection->getResources());
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testImportWithoutLoaderThrowsException()
+ {
+ $collectionBuilder = new RouteCollectionBuilder();
+ $collectionBuilder->import('routing.yml');
+ }
+
+ public function testAdd()
+ {
+ $collectionBuilder = new RouteCollectionBuilder();
+
+ $addedRoute = $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout');
+ $addedRoute2 = $collectionBuilder->add('/blogs', 'AppBundle:Blog:list', 'blog_list');
+ $this->assertInstanceOf('Symfony\Component\Routing\Route', $addedRoute);
+ $this->assertEquals('AppBundle:Order:checkout', $addedRoute->getDefault('_controller'));
+
+ $finalCollection = $collectionBuilder->build();
+ $this->assertSame($addedRoute2, $finalCollection->get('blog_list'));
+ }
+
+ public function testFlushOrdering()
+ {
+ $importedCollection = new RouteCollection();
+ $importedCollection->add('imported_route1', new Route('/imported/foo1'));
+ $importedCollection->add('imported_route2', new Route('/imported/foo2'));
+
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ // make this loader able to do the import - keeps mocking simple
+ $loader->expects($this->any())
+ ->method('supports')
+ ->will($this->returnValue(true));
+ $loader
+ ->expects($this->once())
+ ->method('load')
+ ->will($this->returnValue($importedCollection));
+
+ $routes = new RouteCollectionBuilder($loader);
+
+ // 1) Add a route
+ $routes->add('/checkout', 'AppBundle:Order:checkout', 'checkout_route');
+ // 2) Import from a file
+ $routes->mount('/', $routes->import('admin_routing.yml'));
+ // 3) Add another route
+ $routes->add('/', 'AppBundle:Default:homepage', 'homepage');
+ // 4) Add another route
+ $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard');
+
+ // set a default value
+ $routes->setDefault('_locale', 'fr');
+
+ $actualCollection = $routes->build();
+
+ $this->assertCount(5, $actualCollection);
+ $actualRouteNames = array_keys($actualCollection->all());
+ $this->assertEquals(array(
+ 'checkout_route',
+ 'imported_route1',
+ 'imported_route2',
+ 'homepage',
+ 'admin_dashboard',
+ ), $actualRouteNames);
+
+ // make sure the defaults were set
+ $checkoutRoute = $actualCollection->get('checkout_route');
+ $defaults = $checkoutRoute->getDefaults();
+ $this->assertArrayHasKey('_locale', $defaults);
+ $this->assertEquals('fr', $defaults['_locale']);
+ }
+
+ public function testFlushSetsRouteNames()
+ {
+ $collectionBuilder = new RouteCollectionBuilder();
+
+ // add a "named" route
+ $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard');
+ // add an unnamed route
+ $collectionBuilder->add('/blogs', 'AppBundle:Blog:list')
+ ->setMethods(array('GET'));
+
+ // integer route names are allowed - they don't confuse things
+ $collectionBuilder->add('/products', 'AppBundle:Product:list', 100);
+
+ $actualCollection = $collectionBuilder->build();
+ $actualRouteNames = array_keys($actualCollection->all());
+ $this->assertEquals(array(
+ 'admin_dashboard',
+ 'GET_blogs',
+ '100',
+ ), $actualRouteNames);
+ }
+
+ public function testFlushSetsDetailsOnChildrenRoutes()
+ {
+ $routes = new RouteCollectionBuilder();
+
+ $routes->add('/blogs/{page}', 'listAction', 'blog_list')
+ // unique things for the route
+ ->setDefault('page', 1)
+ ->setRequirement('id', '\d+')
+ ->setOption('expose', true)
+ // things that the collection will try to override (but won't)
+ ->setDefault('_format', 'html')
+ ->setRequirement('_format', 'json|xml')
+ ->setOption('fooBar', true)
+ ->setHost('example.com')
+ ->setCondition('request.isSecure()')
+ ->setSchemes(array('https'))
+ ->setMethods(array('POST'));
+
+ // a simple route, nothing added to it
+ $routes->add('/blogs/{id}', 'editAction', 'blog_edit');
+
+ // configure the collection itself
+ $routes
+ // things that will not override the child route
+ ->setDefault('_format', 'json')
+ ->setRequirement('_format', 'xml')
+ ->setOption('fooBar', false)
+ ->setHost('symfony.com')
+ ->setCondition('request.query.get("page")==1')
+ // some unique things that should be set on the child
+ ->setDefault('_locale', 'fr')
+ ->setRequirement('_locale', 'fr|en')
+ ->setOption('niceRoute', true)
+ ->setSchemes(array('http'))
+ ->setMethods(array('GET', 'POST'));
+
+ $collection = $routes->build();
+ $actualListRoute = $collection->get('blog_list');
+
+ $this->assertEquals(1, $actualListRoute->getDefault('page'));
+ $this->assertEquals('\d+', $actualListRoute->getRequirement('id'));
+ $this->assertTrue($actualListRoute->getOption('expose'));
+ // none of these should be overridden
+ $this->assertEquals('html', $actualListRoute->getDefault('_format'));
+ $this->assertEquals('json|xml', $actualListRoute->getRequirement('_format'));
+ $this->assertTrue($actualListRoute->getOption('fooBar'));
+ $this->assertEquals('example.com', $actualListRoute->getHost());
+ $this->assertEquals('request.isSecure()', $actualListRoute->getCondition());
+ $this->assertEquals(array('https'), $actualListRoute->getSchemes());
+ $this->assertEquals(array('POST'), $actualListRoute->getMethods());
+ // inherited from the main collection
+ $this->assertEquals('fr', $actualListRoute->getDefault('_locale'));
+ $this->assertEquals('fr|en', $actualListRoute->getRequirement('_locale'));
+ $this->assertTrue($actualListRoute->getOption('niceRoute'));
+
+ $actualEditRoute = $collection->get('blog_edit');
+ // inherited from the collection
+ $this->assertEquals('symfony.com', $actualEditRoute->getHost());
+ $this->assertEquals('request.query.get("page")==1', $actualEditRoute->getCondition());
+ $this->assertEquals(array('http'), $actualEditRoute->getSchemes());
+ $this->assertEquals(array('GET', 'POST'), $actualEditRoute->getMethods());
+ }
+
+ /**
+ * @dataProvider providePrefixTests
+ */
+ public function testFlushPrefixesPaths($collectionPrefix, $routePath, $expectedPath)
+ {
+ $routes = new RouteCollectionBuilder();
+
+ $routes->add($routePath, 'someController', 'test_route');
+
+ $outerRoutes = new RouteCollectionBuilder();
+ $outerRoutes->mount($collectionPrefix, $routes);
+
+ $collection = $outerRoutes->build();
+
+ $this->assertEquals($expectedPath, $collection->get('test_route')->getPath());
+ }
+
+ public function providePrefixTests()
+ {
+ $tests = array();
+ // empty prefix is of course ok
+ $tests[] = array('', '/foo', '/foo');
+ // normal prefix - does not matter if it's a wildcard
+ $tests[] = array('/{admin}', '/foo', '/{admin}/foo');
+ // shows that a prefix will always be given the starting slash
+ $tests[] = array('0', '/foo', '/0/foo');
+
+ // spaces are ok, and double slahses at the end are cleaned
+ $tests[] = array('/ /', '/foo', '/ /foo');
+
+ return $tests;
+ }
+
+ public function testFlushSetsPrefixedWithMultipleLevels()
+ {
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $routes = new RouteCollectionBuilder($loader);
+
+ $routes->add('homepage', 'MainController::homepageAction', 'homepage');
+
+ $adminRoutes = $routes->createBuilder();
+ $adminRoutes->add('/dashboard', 'AdminController::dashboardAction', 'admin_dashboard');
+
+ // embedded collection under /admin
+ $adminBlogRoutes = $routes->createBuilder();
+ $adminBlogRoutes->add('/new', 'BlogController::newAction', 'admin_blog_new');
+ // mount into admin, but before the parent collection has been mounted
+ $adminRoutes->mount('/blog', $adminBlogRoutes);
+
+ // now mount the /admin routes, above should all still be /blog/admin
+ $routes->mount('/admin', $adminRoutes);
+ // add a route after mounting
+ $adminRoutes->add('/users', 'AdminController::userAction', 'admin_users');
+
+ // add another sub-collection after the mount
+ $otherAdminRoutes = $routes->createBuilder();
+ $otherAdminRoutes->add('/sales', 'StatsController::indexAction', 'admin_stats_sales');
+ $adminRoutes->mount('/stats', $otherAdminRoutes);
+
+ // add a normal collection and see that it is also prefixed
+ $importedCollection = new RouteCollection();
+ $importedCollection->add('imported_route', new Route('/foo'));
+ // make this loader able to do the import - keeps mocking simple
+ $loader->expects($this->any())
+ ->method('supports')
+ ->will($this->returnValue(true));
+ $loader
+ ->expects($this->any())
+ ->method('load')
+ ->will($this->returnValue($importedCollection));
+ // import this from the /admin route builder
+ $adminRoutes->import('admin.yml', '/imported');
+
+ $collection = $routes->build();
+ $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix');
+ $this->assertEquals('/admin/users', $collection->get('admin_users')->getPath(), 'Routes after mounting have the prefix');
+ $this->assertEquals('/admin/blog/new', $collection->get('admin_blog_new')->getPath(), 'Sub-collections receive prefix even if mounted before parent prefix');
+ $this->assertEquals('/admin/stats/sales', $collection->get('admin_stats_sales')->getPath(), 'Sub-collections receive prefix if mounted after parent prefix');
+ $this->assertEquals('/admin/imported/foo', $collection->get('imported_route')->getPath(), 'Normal RouteCollections are also prefixed properly');
+ }
+
+ public function testAutomaticRouteNamesDoNotConflict()
+ {
+ $routes = new RouteCollectionBuilder();
+
+ $adminRoutes = $routes->createBuilder();
+ // route 1
+ $adminRoutes->add('/dashboard', '');
+
+ $accountRoutes = $routes->createBuilder();
+ // route 2
+ $accountRoutes->add('/dashboard', '')
+ ->setMethods(array('GET'));
+ // route 3
+ $accountRoutes->add('/dashboard', '')
+ ->setMethods(array('POST'));
+
+ $routes->mount('/admin', $adminRoutes);
+ $routes->mount('/account', $accountRoutes);
+
+ $collection = $routes->build();
+ // there are 2 routes (i.e. with non-conflicting names)
+ $this->assertCount(3, $collection->all());
+ }
+
+ public function testAddsThePrefixOnlyOnceWhenLoadingMultipleCollections()
+ {
+ $firstCollection = new RouteCollection();
+ $firstCollection->add('a', new Route('/a'));
+
+ $secondCollection = new RouteCollection();
+ $secondCollection->add('b', new Route('/b'));
+
+ $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock();
+ $loader->expects($this->any())
+ ->method('supports')
+ ->will($this->returnValue(true));
+ $loader
+ ->expects($this->any())
+ ->method('load')
+ ->will($this->returnValue(array($firstCollection, $secondCollection)));
+
+ $routeCollectionBuilder = new RouteCollectionBuilder($loader);
+ $routeCollectionBuilder->import('/directory/recurse/*', '/other/', 'glob');
+ $routes = $routeCollectionBuilder->build()->all();
+
+ $this->assertCount(2, $routes);
+ $this->assertEquals('/other/a', $routes['a']->getPath());
+ $this->assertEquals('/other/b', $routes['b']->getPath());
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/RouteCollectionTest.php b/assets/php/vendor/symfony/routing/Tests/RouteCollectionTest.php
new file mode 100644
index 0000000..83457ff
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/RouteCollectionTest.php
@@ -0,0 +1,305 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Config\Resource\FileResource;
+
+class RouteCollectionTest extends TestCase
+{
+ public function testRoute()
+ {
+ $collection = new RouteCollection();
+ $route = new Route('/foo');
+ $collection->add('foo', $route);
+ $this->assertEquals(array('foo' => $route), $collection->all(), '->add() adds a route');
+ $this->assertEquals($route, $collection->get('foo'), '->get() returns a route by name');
+ $this->assertNull($collection->get('bar'), '->get() returns null if a route does not exist');
+ }
+
+ public function testOverriddenRoute()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo'));
+ $collection->add('foo', new Route('/foo1'));
+
+ $this->assertEquals('/foo1', $collection->get('foo')->getPath());
+ }
+
+ public function testDeepOverriddenRoute()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo'));
+
+ $collection1 = new RouteCollection();
+ $collection1->add('foo', new Route('/foo1'));
+
+ $collection2 = new RouteCollection();
+ $collection2->add('foo', new Route('/foo2'));
+
+ $collection1->addCollection($collection2);
+ $collection->addCollection($collection1);
+
+ $this->assertEquals('/foo2', $collection1->get('foo')->getPath());
+ $this->assertEquals('/foo2', $collection->get('foo')->getPath());
+ }
+
+ public function testIterator()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo'));
+
+ $collection1 = new RouteCollection();
+ $collection1->add('bar', $bar = new Route('/bar'));
+ $collection1->add('foo', $foo = new Route('/foo-new'));
+ $collection->addCollection($collection1);
+ $collection->add('last', $last = new Route('/last'));
+
+ $this->assertInstanceOf('\ArrayIterator', $collection->getIterator());
+ $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'last' => $last), $collection->getIterator()->getArrayCopy());
+ }
+
+ public function testCount()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo'));
+
+ $collection1 = new RouteCollection();
+ $collection1->add('bar', new Route('/bar'));
+ $collection->addCollection($collection1);
+
+ $this->assertCount(2, $collection);
+ }
+
+ public function testAddCollection()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo'));
+
+ $collection1 = new RouteCollection();
+ $collection1->add('bar', $bar = new Route('/bar'));
+ $collection1->add('foo', $foo = new Route('/foo-new'));
+
+ $collection2 = new RouteCollection();
+ $collection2->add('grandchild', $grandchild = new Route('/grandchild'));
+
+ $collection1->addCollection($collection2);
+ $collection->addCollection($collection1);
+ $collection->add('last', $last = new Route('/last'));
+
+ $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'grandchild' => $grandchild, 'last' => $last), $collection->all(),
+ '->addCollection() imports routes of another collection, overrides if necessary and adds them at the end');
+ }
+
+ public function testAddCollectionWithResources()
+ {
+ $collection = new RouteCollection();
+ $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml'));
+ $collection1 = new RouteCollection();
+ $collection1->addResource($foo1 = new FileResource(__DIR__.'/Fixtures/foo1.xml'));
+ $collection->addCollection($collection1);
+ $this->assertEquals(array($foo, $foo1), $collection->getResources(), '->addCollection() merges resources');
+ }
+
+ public function testAddDefaultsAndRequirementsAndOptions()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/{placeholder}'));
+ $collection1 = new RouteCollection();
+ $collection1->add('bar', new Route('/{placeholder}',
+ array('_controller' => 'fixed', 'placeholder' => 'default'), array('placeholder' => '.+'), array('option' => 'value'))
+ );
+ $collection->addCollection($collection1);
+
+ $collection->addDefaults(array('placeholder' => 'new-default'));
+ $this->assertEquals(array('placeholder' => 'new-default'), $collection->get('foo')->getDefaults(), '->addDefaults() adds defaults to all routes');
+ $this->assertEquals(array('_controller' => 'fixed', 'placeholder' => 'new-default'), $collection->get('bar')->getDefaults(),
+ '->addDefaults() adds defaults to all routes and overwrites existing ones');
+
+ $collection->addRequirements(array('placeholder' => '\d+'));
+ $this->assertEquals(array('placeholder' => '\d+'), $collection->get('foo')->getRequirements(), '->addRequirements() adds requirements to all routes');
+ $this->assertEquals(array('placeholder' => '\d+'), $collection->get('bar')->getRequirements(),
+ '->addRequirements() adds requirements to all routes and overwrites existing ones');
+
+ $collection->addOptions(array('option' => 'new-value'));
+ $this->assertEquals(
+ array('option' => 'new-value', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'),
+ $collection->get('bar')->getOptions(), '->addOptions() adds options to all routes and overwrites existing ones'
+ );
+ }
+
+ public function testAddPrefix()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', $foo = new Route('/foo'));
+ $collection2 = new RouteCollection();
+ $collection2->add('bar', $bar = new Route('/bar'));
+ $collection->addCollection($collection2);
+ $collection->addPrefix(' / ');
+ $this->assertSame('/foo', $collection->get('foo')->getPath(), '->addPrefix() trims the prefix and a single slash has no effect');
+ $collection->addPrefix('/{admin}', array('admin' => 'admin'), array('admin' => '\d+'));
+ $this->assertEquals('/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() adds a prefix to all routes');
+ $this->assertEquals('/{admin}/bar', $collection->get('bar')->getPath(), '->addPrefix() adds a prefix to all routes');
+ $this->assertEquals(array('admin' => 'admin'), $collection->get('foo')->getDefaults(), '->addPrefix() adds defaults to all routes');
+ $this->assertEquals(array('admin' => 'admin'), $collection->get('bar')->getDefaults(), '->addPrefix() adds defaults to all routes');
+ $this->assertEquals(array('admin' => '\d+'), $collection->get('foo')->getRequirements(), '->addPrefix() adds requirements to all routes');
+ $this->assertEquals(array('admin' => '\d+'), $collection->get('bar')->getRequirements(), '->addPrefix() adds requirements to all routes');
+ $collection->addPrefix('0');
+ $this->assertEquals('/0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() ensures a prefix must start with a slash and must not end with a slash');
+ $collection->addPrefix('/ /');
+ $this->assertSame('/ /0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() can handle spaces if desired');
+ $this->assertSame('/ /0/{admin}/bar', $collection->get('bar')->getPath(), 'the route pattern of an added collection is in synch with the added prefix');
+ }
+
+ public function testAddPrefixOverridesDefaultsAndRequirements()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', $foo = new Route('/foo.{_format}'));
+ $collection->add('bar', $bar = new Route('/bar.{_format}', array(), array('_format' => 'json')));
+ $collection->addPrefix('/admin', array(), array('_format' => 'html'));
+
+ $this->assertEquals('html', $collection->get('foo')->getRequirement('_format'), '->addPrefix() overrides existing requirements');
+ $this->assertEquals('html', $collection->get('bar')->getRequirement('_format'), '->addPrefix() overrides existing requirements');
+ }
+
+ public function testResource()
+ {
+ $collection = new RouteCollection();
+ $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml'));
+ $collection->addResource($bar = new FileResource(__DIR__.'/Fixtures/bar.xml'));
+ $collection->addResource(new FileResource(__DIR__.'/Fixtures/foo.xml'));
+
+ $this->assertEquals(array($foo, $bar), $collection->getResources(),
+ '->addResource() adds a resource and getResources() only returns unique ones by comparing the string representation');
+ }
+
+ public function testUniqueRouteWithGivenName()
+ {
+ $collection1 = new RouteCollection();
+ $collection1->add('foo', new Route('/old'));
+ $collection2 = new RouteCollection();
+ $collection3 = new RouteCollection();
+ $collection3->add('foo', $new = new Route('/new'));
+
+ $collection2->addCollection($collection3);
+ $collection1->addCollection($collection2);
+
+ $this->assertSame($new, $collection1->get('foo'), '->get() returns new route that overrode previous one');
+ // size of 1 because collection1 contains /new but not /old anymore
+ $this->assertCount(1, $collection1->getIterator(), '->addCollection() removes previous routes when adding new routes with the same name');
+ }
+
+ public function testGet()
+ {
+ $collection1 = new RouteCollection();
+ $collection1->add('a', $a = new Route('/a'));
+ $collection2 = new RouteCollection();
+ $collection2->add('b', $b = new Route('/b'));
+ $collection1->addCollection($collection2);
+ $collection1->add('$péß^a|', $c = new Route('/special'));
+
+ $this->assertSame($b, $collection1->get('b'), '->get() returns correct route in child collection');
+ $this->assertSame($c, $collection1->get('$péß^a|'), '->get() can handle special characters');
+ $this->assertNull($collection2->get('a'), '->get() does not return the route defined in parent collection');
+ $this->assertNull($collection1->get('non-existent'), '->get() returns null when route does not exist');
+ $this->assertNull($collection1->get(0), '->get() does not disclose internal child RouteCollection');
+ }
+
+ public function testRemove()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', $foo = new Route('/foo'));
+
+ $collection1 = new RouteCollection();
+ $collection1->add('bar', $bar = new Route('/bar'));
+ $collection->addCollection($collection1);
+ $collection->add('last', $last = new Route('/last'));
+
+ $collection->remove('foo');
+ $this->assertSame(array('bar' => $bar, 'last' => $last), $collection->all(), '->remove() can remove a single route');
+ $collection->remove(array('bar', 'last'));
+ $this->assertSame(array(), $collection->all(), '->remove() accepts an array and can remove multiple routes at once');
+ }
+
+ public function testSetHost()
+ {
+ $collection = new RouteCollection();
+ $routea = new Route('/a');
+ $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net');
+ $collection->add('a', $routea);
+ $collection->add('b', $routeb);
+
+ $collection->setHost('{locale}.example.com');
+
+ $this->assertEquals('{locale}.example.com', $routea->getHost());
+ $this->assertEquals('{locale}.example.com', $routeb->getHost());
+ }
+
+ public function testSetCondition()
+ {
+ $collection = new RouteCollection();
+ $routea = new Route('/a');
+ $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net', array(), array(), 'context.getMethod() == "GET"');
+ $collection->add('a', $routea);
+ $collection->add('b', $routeb);
+
+ $collection->setCondition('context.getMethod() == "POST"');
+
+ $this->assertEquals('context.getMethod() == "POST"', $routea->getCondition());
+ $this->assertEquals('context.getMethod() == "POST"', $routeb->getCondition());
+ }
+
+ public function testClone()
+ {
+ $collection = new RouteCollection();
+ $collection->add('a', new Route('/a'));
+ $collection->add('b', new Route('/b', array('placeholder' => 'default'), array('placeholder' => '.+')));
+
+ $clonedCollection = clone $collection;
+
+ $this->assertCount(2, $clonedCollection);
+ $this->assertEquals($collection->get('a'), $clonedCollection->get('a'));
+ $this->assertNotSame($collection->get('a'), $clonedCollection->get('a'));
+ $this->assertEquals($collection->get('b'), $clonedCollection->get('b'));
+ $this->assertNotSame($collection->get('b'), $clonedCollection->get('b'));
+ }
+
+ public function testSetSchemes()
+ {
+ $collection = new RouteCollection();
+ $routea = new Route('/a', array(), array(), array(), '', 'http');
+ $routeb = new Route('/b');
+ $collection->add('a', $routea);
+ $collection->add('b', $routeb);
+
+ $collection->setSchemes(array('http', 'https'));
+
+ $this->assertEquals(array('http', 'https'), $routea->getSchemes());
+ $this->assertEquals(array('http', 'https'), $routeb->getSchemes());
+ }
+
+ public function testSetMethods()
+ {
+ $collection = new RouteCollection();
+ $routea = new Route('/a', array(), array(), array(), '', array(), array('GET', 'POST'));
+ $routeb = new Route('/b');
+ $collection->add('a', $routea);
+ $collection->add('b', $routeb);
+
+ $collection->setMethods('PUT');
+
+ $this->assertEquals(array('PUT'), $routea->getMethods());
+ $this->assertEquals(array('PUT'), $routeb->getMethods());
+ }
+}
diff --git a/assets/php/vendor/symfony/routing/Tests/RouteCompilerTest.php b/assets/php/vendor/symfony/routing/Tests/RouteCompilerTest.php
new file mode 100644
index 0000000..dc304e3
--- /dev/null
+++ b/assets/php/vendor/symfony/routing/Tests/RouteCompilerTest.php
@@ -0,0 +1,389 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCompiler;
+
+class RouteCompilerTest extends TestCase
+{
+ /**
+ * @dataProvider provideCompileData
+ */
+ public function testCompile($name, $arguments, $prefix, $regex, $variables, $tokens)
+ {
+ $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route');
+ $route = $r->newInstanceArgs($arguments);
+
+ $compiled = $route->compile();
+ $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)');
+ $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)');
+ $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)');
+ $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)');
+ }
+
+ public function provideCompileData()
+ {
+ return array(
+ array(
+ 'Static route',
+ array('/foo'),
+ '/foo', '#^/foo$#sD', array(), array(
+ array('text', '/foo'),
+ ),
+ ),
+
+ array(
+ 'Route with a variable',
+ array('/foo/{bar}'),
+ '/foo', '#^/foo/(?P[^/]++)$#sD', array('bar'), array(
+ array('variable', '/', '[^/]++', 'bar'),
+ array('text', '/foo'),
+ ),
+ ),
+
+ array(
+ 'Route with a variable that has a default value',
+ array('/foo/{bar}', array('bar' => 'bar')),
+ '/foo', '#^/foo(?:/(?P[^/]++))?$#sD', array('bar'), array(
+ array('variable', '/', '[^/]++', 'bar'),
+ array('text', '/foo'),
+ ),
+ ),
+
+ array(
+ 'Route with several variables',
+ array('/foo/{bar}/{foobar}'),
+ '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#sD', array('bar', 'foobar'), array(
+ array('variable', '/', '[^/]++', 'foobar'),
+ array('variable', '/', '[^/]++', 'bar'),
+ array('text', '/foo'),
+ ),
+ ),
+
+ array(
+ 'Route with several variables that have default values',
+ array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')),
+ '/foo', '#^/foo(?:/(?P[^/]++)(?:/(?P[^/]++))?)?$#sD', array('bar', 'foobar'), array(
+ array('variable', '/', '[^/]++', 'foobar'),
+ array('variable', '/', '[^/]++', 'bar'),
+ array('text', '/foo'),
+ ),
+ ),
+
+ array(
+ 'Route with several variables but some of them have no default values',
+ array('/foo/{bar}/{foobar}', array('bar' => 'bar')),
+ '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#sD', array('bar', 'foobar'), array(
+ array('variable', '/', '[^/]++', 'foobar'),
+ array('variable', '/', '[^/]++', 'bar'),
+ array('text', '/foo'),
+ ),
+ ),
+
+ array(
+ 'Route with an optional variable as the first segment',
+ array('/{bar}', array('bar' => 'bar')),
+ '', '#^/(?P[^/]++)?$#sD', array('bar'), array(
+ array('variable', '/', '[^/]++', 'bar'),
+ ),
+ ),
+
+ array(
+ 'Route with a requirement of 0',
+ array('/{bar}', array('bar' => null), array('bar' => '0')),
+ '', '#^/(?P0)?$#sD', array('bar'), array(
+ array('variable', '/', '0', 'bar'),
+ ),
+ ),
+
+ array(
+ 'Route with an optional variable as the first segment with requirements',
+ array('/{bar}', array('bar' => 'bar'), array('bar' => '(foo|bar)')),
+ '', '#^/(?P(foo|bar))?$#sD', array('bar'), array(
+ array('variable', '/', '(foo|bar)', 'bar'),
+ ),
+ ),
+
+ array(
+ 'Route with only optional variables',
+ array('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar')),
+ '', '#^/(?P[^/]++)?(?:/(?P[^/]++))?$#sD', array('foo', 'bar'), array(
+ array('variable', '/', '[^/]++', 'bar'),
+ array('variable', '/', '[^/]++', 'foo'),
+ ),
+ ),
+
+ array(
+ 'Route with a variable in last position',
+ array('/foo-{bar}'),
+ '/foo-', '#^/foo\-(?P[^/]++)$#sD', array('bar'), array(
+ array('variable', '-', '[^/]++', 'bar'),
+ array('text', '/foo'),
+ ),
+ ),
+
+ array(
+ 'Route with nested placeholders',
+ array('/{static{var}static}'),
+ '/{static', '#^/\{static(?P[^/]+)static\}$#sD', array('var'), array(
+ array('text', 'static}'),
+ array('variable', '', '[^/]+', 'var'),
+ array('text', '/{static'),
+ ),
+ ),
+
+ array(
+ 'Route without separator between variables',
+ array('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '(y|Y)')),
+ '', '#^/(?P[^/\.]+)(?P[^/\.]+)(?P(y|Y))(?:(?P[^/\.]++)(?:\.(?P<_format>[^/]++))?)?$#sD', array('w', 'x', 'y', 'z', '_format'), array(
+ array('variable', '.', '[^/]++', '_format'),
+ array('variable', '', '[^/\.]++', 'z'),
+ array('variable', '', '(y|Y)', 'y'),
+ array('variable', '', '[^/\.]+', 'x'),
+ array('variable', '/', '[^/\.]+', 'w'),
+ ),
+ ),
+
+ array(
+ 'Route with a format',
+ array('/foo/{bar}.{_format}'),
+ '/foo', '#^/foo/(?P[^/\.]++)\.(?P<_format>[^/]++)$#sD', array('bar', '_format'), array(
+ array('variable', '.', '[^/]++', '_format'),
+ array('variable', '/', '[^/\.]++', 'bar'),
+ array('text', '/foo'),
+ ),
+ ),
+
+ array(
+ 'Static non UTF-8 route',
+ array("/fo\xE9"),
+ "/fo\xE9", "#^/fo\xE9$#sD", array(), array(
+ array('text', "/fo\xE9"),
+ ),
+ ),
+
+ array(
+ 'Route with an explicit UTF-8 requirement',
+ array('/{bar}', array('bar' => null), array('bar' => '.'), array('utf8' => true)),
+ '', '#^/(?P