*/
-class MercurialProcessor
+class MercurialProcessor implements ProcessorInterface
{
private $level;
private static $cache;
diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php
index 9d3f5590f..66b80fbbd 100644
--- a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php
+++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php
@@ -16,7 +16,7 @@ namespace Monolog\Processor;
*
* @author Andreas Hörnicke
*/
-class ProcessIdProcessor
+class ProcessIdProcessor implements ProcessorInterface
{
/**
* @param array $record
diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php
new file mode 100644
index 000000000..7e64d4dfa
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+/**
+ * An optional interface to allow labelling Monolog processors.
+ *
+ * @author Nicolas Grekas
+ */
+interface ProcessorInterface
+{
+ /**
+ * @return array The processed records
+ */
+ public function __invoke(array $records);
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php
index c2686ce5b..008850545 100644
--- a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php
+++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php
@@ -11,6 +11,8 @@
namespace Monolog\Processor;
+use Monolog\Utils;
+
/**
* Processes a record's message according to PSR-3 rules
*
@@ -18,7 +20,7 @@ namespace Monolog\Processor;
*
* @author Jordi Boggiano
*/
-class PsrLogMessageProcessor
+class PsrLogMessageProcessor implements ProcessorInterface
{
/**
* @param array $record
@@ -35,7 +37,7 @@ class PsrLogMessageProcessor
if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) {
$replacements['{'.$key.'}'] = $val;
} elseif (is_object($val)) {
- $replacements['{'.$key.'}'] = '[object '.get_class($val).']';
+ $replacements['{'.$key.'}'] = '[object '.Utils::getClass($val).']';
} else {
$replacements['{'.$key.'}'] = '['.gettype($val).']';
}
diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php
index 7e2df2acb..615a4d991 100644
--- a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php
+++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php
@@ -16,7 +16,7 @@ namespace Monolog\Processor;
*
* @author Martijn Riemers
*/
-class TagProcessor
+class TagProcessor implements ProcessorInterface
{
private $tags;
diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php
index 812707cdb..d1f708cf2 100644
--- a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php
+++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php
@@ -11,12 +11,14 @@
namespace Monolog\Processor;
+use Monolog\ResettableInterface;
+
/**
* Adds a unique identifier into records
*
* @author Simon Mönch
*/
-class UidProcessor
+class UidProcessor implements ProcessorInterface, ResettableInterface
{
private $uid;
@@ -26,7 +28,8 @@ class UidProcessor
throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32');
}
- $this->uid = substr(hash('md5', uniqid('', true)), 0, $length);
+
+ $this->uid = $this->generateUid($length);
}
public function __invoke(array $record)
@@ -43,4 +46,14 @@ class UidProcessor
{
return $this->uid;
}
+
+ public function reset()
+ {
+ $this->uid = $this->generateUid(strlen($this->uid));
+ }
+
+ private function generateUid($length)
+ {
+ return substr(hash('md5', uniqid('', true)), 0, $length);
+ }
}
diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php
index ea1d89782..684188f66 100644
--- a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php
+++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php
@@ -16,7 +16,7 @@ namespace Monolog\Processor;
*
* @author Jordi Boggiano
*/
-class WebProcessor
+class WebProcessor implements ProcessorInterface
{
/**
* @var array|\ArrayAccess
diff --git a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php
new file mode 100644
index 000000000..635bc77dc
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.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 Monolog;
+
+/**
+ * Handler or Processor implementing this interface will be reset when Logger::reset() is called.
+ *
+ * Resetting ends a log cycle gets them back to their initial state.
+ *
+ * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal
+ * state, and getting it back to a state in which it can receive log records again.
+ *
+ * This is useful in case you want to avoid logs leaking between two requests or jobs when you
+ * have a long running process like a worker or an application server serving multiple requests
+ * in one process.
+ *
+ * @author Grégoire Pineau
+ */
+interface ResettableInterface
+{
+ public function reset();
+}
diff --git a/vendor/monolog/monolog/src/Monolog/SignalHandler.php b/vendor/monolog/monolog/src/Monolog/SignalHandler.php
new file mode 100644
index 000000000..d5907805a
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/SignalHandler.php
@@ -0,0 +1,115 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+use ReflectionExtension;
+
+/**
+ * Monolog POSIX signal handler
+ *
+ * @author Robert Gust-Bardon
+ */
+class SignalHandler
+{
+ private $logger;
+
+ private $previousSignalHandler = array();
+ private $signalLevelMap = array();
+ private $signalRestartSyscalls = array();
+
+ public function __construct(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true)
+ {
+ if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) {
+ return $this;
+ }
+
+ if ($callPrevious) {
+ if (function_exists('pcntl_signal_get_handler')) {
+ $handler = pcntl_signal_get_handler($signo);
+ if ($handler === false) {
+ return $this;
+ }
+ $this->previousSignalHandler[$signo] = $handler;
+ } else {
+ $this->previousSignalHandler[$signo] = true;
+ }
+ } else {
+ unset($this->previousSignalHandler[$signo]);
+ }
+ $this->signalLevelMap[$signo] = $level;
+ $this->signalRestartSyscalls[$signo] = $restartSyscalls;
+
+ if (function_exists('pcntl_async_signals') && $async !== null) {
+ pcntl_async_signals($async);
+ }
+
+ pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
+
+ return $this;
+ }
+
+ public function handleSignal($signo, array $siginfo = null)
+ {
+ static $signals = array();
+
+ if (!$signals && extension_loaded('pcntl')) {
+ $pcntl = new ReflectionExtension('pcntl');
+ $constants = $pcntl->getConstants();
+ if (!$constants) {
+ // HHVM 3.24.2 returns an empty array.
+ $constants = get_defined_constants(true);
+ $constants = $constants['Core'];
+ }
+ foreach ($constants as $name => $value) {
+ if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) {
+ $signals[$value] = $name;
+ }
+ }
+ unset($constants);
+ }
+
+ $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL;
+ $signal = isset($signals[$signo]) ? $signals[$signo] : $signo;
+ $context = isset($siginfo) ? $siginfo : array();
+ $this->logger->log($level, sprintf('Program received signal %s', $signal), $context);
+
+ if (!isset($this->previousSignalHandler[$signo])) {
+ return;
+ }
+
+ if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) {
+ if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch')
+ && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) {
+ $restartSyscalls = isset($this->restartSyscalls[$signo]) ? $this->restartSyscalls[$signo] : true;
+ pcntl_signal($signo, SIG_DFL, $restartSyscalls);
+ pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset);
+ posix_kill(posix_getpid(), $signo);
+ pcntl_signal_dispatch();
+ pcntl_sigprocmask(SIG_SETMASK, $oldset);
+ pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
+ }
+ } elseif (is_callable($this->previousSignalHandler[$signo])) {
+ if (PHP_VERSION_ID >= 70100) {
+ $this->previousSignalHandler[$signo]($signo, $siginfo);
+ } else {
+ $this->previousSignalHandler[$signo]($signo);
+ }
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php
new file mode 100644
index 000000000..eb9be863b
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Utils.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+class Utils
+{
+ /**
+ * @internal
+ */
+ public static function getClass($object)
+ {
+ $class = \get_class($object);
+
+ return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
+ }
+}
diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php
index 622b2bae2..fd36dbcf4 100644
--- a/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php
@@ -40,7 +40,7 @@ class FluentdFormatterTest extends TestCase
$formatter = new FluentdFormatter();
$this->assertEquals(
- '["test",0,{"message":"test","extra":[],"level":300,"level_name":"WARNING"}]',
+ '["test",0,{"message":"test","context":[],"extra":[],"level":300,"level_name":"WARNING"}]',
$formatter->format($record)
);
}
@@ -55,7 +55,7 @@ class FluentdFormatterTest extends TestCase
$formatter = new FluentdFormatter(true);
$this->assertEquals(
- '["test.error",0,{"message":"test","extra":[]}]',
+ '["test.error",0,{"message":"test","context":[],"extra":[]}]',
$formatter->format($record)
);
}
diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php
index c9445f363..24b06cc94 100644
--- a/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php
@@ -180,4 +180,40 @@ class JsonFormatterTest extends TestCase
'}';
return $formattedException;
}
+
+ public function testNormalizeHandleLargeArraysWithExactly1000Items()
+ {
+ $formatter = new NormalizerFormatter();
+ $largeArray = range(1, 1000);
+
+ $res = $formatter->format(array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'test',
+ 'message' => 'bar',
+ 'context' => array($largeArray),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ));
+
+ $this->assertCount(1000, $res['context'][0]);
+ $this->assertArrayNotHasKey('...', $res['context'][0]);
+ }
+
+ public function testNormalizeHandleLargeArrays()
+ {
+ $formatter = new NormalizerFormatter();
+ $largeArray = range(1, 2000);
+
+ $res = $formatter->format(array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'test',
+ 'message' => 'bar',
+ 'context' => array($largeArray),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ));
+
+ $this->assertCount(1001, $res['context'][0]);
+ $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);
+ }
}
diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php
index 57bcdf984..bafd1c74b 100644
--- a/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php
@@ -193,6 +193,15 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(@json_encode(array($foo, $bar)), $res);
}
+ public function testCanNormalizeReferences()
+ {
+ $formatter = new NormalizerFormatter();
+ $x = array('foo' => 'bar');
+ $y = array('x' => &$x);
+ $x['y'] = &$y;
+ $formatter->format($y);
+ }
+
public function testIgnoresInvalidTypes()
{
// set up the recursion
@@ -217,6 +226,24 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(@json_encode(array($resource)), $res);
}
+ public function testNormalizeHandleLargeArraysWithExactly1000Items()
+ {
+ $formatter = new NormalizerFormatter();
+ $largeArray = range(1, 1000);
+
+ $res = $formatter->format(array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'test',
+ 'message' => 'bar',
+ 'context' => array($largeArray),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ));
+
+ $this->assertCount(1000, $res['context'][0]);
+ $this->assertArrayNotHasKey('...', $res['context'][0]);
+ }
+
public function testNormalizeHandleLargeArrays()
{
$formatter = new NormalizerFormatter();
@@ -231,7 +258,7 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
'extra' => array(),
));
- $this->assertCount(1000, $res['context'][0]);
+ $this->assertCount(1001, $res['context'][0]);
$this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);
}
@@ -380,6 +407,29 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
$result['context']['exception']['trace'][0]
);
}
+
+ public function testExceptionTraceDoesNotLeakCallUserFuncArgs()
+ {
+ try {
+ $arg = new TestInfoLeak;
+ call_user_func(array($this, 'throwHelper'), $arg, $dt = new \DateTime());
+ } catch (\Exception $e) {
+ }
+
+ $formatter = new NormalizerFormatter();
+ $record = array('context' => array('exception' => $e));
+ $result = $formatter->format($record);
+
+ $this->assertSame(
+ '{"function":"throwHelper","class":"Monolog\\\\Formatter\\\\NormalizerFormatterTest","type":"->","args":["[object] (Monolog\\\\Formatter\\\\TestInfoLeak)","'.$dt->format('Y-m-d H:i:s').'"]}',
+ $result['context']['exception']['trace'][0]
+ );
+ }
+
+ private function throwHelper($arg)
+ {
+ throw new \RuntimeException('Thrown');
+ }
}
class TestFooNorm
@@ -421,3 +471,11 @@ class TestToStringError
throw new \RuntimeException('Could not convert to string');
}
}
+
+class TestInfoLeak
+{
+ public function __toString()
+ {
+ return 'Sensitive information';
+ }
+}
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php
index ffb1d746a..ffe45da2f 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php
@@ -21,7 +21,7 @@ class BrowserConsoleHandlerTest extends TestCase
{
protected function setUp()
{
- BrowserConsoleHandler::reset();
+ BrowserConsoleHandler::resetStatic();
}
protected function generateScript()
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php
index 0449f8b1a..421cc4918 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php
@@ -21,7 +21,7 @@ class ChromePHPHandlerTest extends TestCase
{
protected function setUp()
{
- TestChromePHPHandler::reset();
+ TestChromePHPHandler::resetStatic();
$_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0';
}
@@ -136,7 +136,7 @@ class TestChromePHPHandler extends ChromePHPHandler
{
protected $headers = array();
- public static function reset()
+ public static function resetStatic()
{
self::$initialized = false;
self::$overflowed = false;
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php
index b92bf437b..0ec36531a 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php
@@ -58,7 +58,7 @@ class FingersCrossedHandlerTest extends TestCase
* @covers Monolog\Handler\FingersCrossedHandler::activate
* @covers Monolog\Handler\FingersCrossedHandler::reset
*/
- public function testHandleRestartBufferingAfterReset()
+ public function testHandleResetBufferingAfterReset()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test);
@@ -76,7 +76,7 @@ class FingersCrossedHandlerTest extends TestCase
* @covers Monolog\Handler\FingersCrossedHandler::handle
* @covers Monolog\Handler\FingersCrossedHandler::activate
*/
- public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
+ public function testHandleResetBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false);
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php
index 0eb10a63f..7a404e660 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php
@@ -21,7 +21,7 @@ class FirePHPHandlerTest extends TestCase
{
public function setUp()
{
- TestFirePHPHandler::reset();
+ TestFirePHPHandler::resetStatic();
$_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0';
}
@@ -77,7 +77,7 @@ class TestFirePHPHandler extends FirePHPHandler
{
protected $headers = array();
- public static function reset()
+ public static function resetStatic()
{
self::$initialized = false;
self::$sendHeaders = true;
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php
new file mode 100644
index 000000000..97c18b592
--- /dev/null
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+ namespace Monolog\Handler;
+
+ use Monolog\TestCase;
+ use Monolog\Logger;
+
+/**
+ * @author Robert Kaufmann III
+ * @author Gabriel Machado
+ */
+class InsightOpsHandlerTest extends TestCase
+{
+ /**
+ * @var resource
+ */
+ private $resource;
+
+ /**
+ * @var LogEntriesHandler
+ */
+ private $handler;
+
+ public function testWriteContent()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test'));
+
+ fseek($this->resource, 0);
+ $content = fread($this->resource, 1024);
+
+ $this->assertRegexp('/testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] test.CRITICAL: Critical write test/', $content);
+ }
+
+ public function testWriteBatchContent()
+ {
+ $this->createHandler();
+ $this->handler->handleBatch($this->getMultipleRecords());
+
+ fseek($this->resource, 0);
+ $content = fread($this->resource, 1024);
+
+ $this->assertRegexp('/(testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] .* \[\] \[\]\n){3}/', $content);
+ }
+
+ private function createHandler()
+ {
+ $useSSL = extension_loaded('openssl');
+ $args = array('testToken', 'us', $useSSL, Logger::DEBUG, true);
+ $this->resource = fopen('php://memory', 'a');
+ $this->handler = $this->getMock(
+ '\Monolog\Handler\InsightOpsHandler',
+ array('fsockopen', 'streamSetTimeout', 'closeSocket'),
+ $args
+ );
+
+ $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString');
+ $reflectionProperty->setAccessible(true);
+ $reflectionProperty->setValue($this->handler, 'localhost:1234');
+
+ $this->handler->expects($this->any())
+ ->method('fsockopen')
+ ->will($this->returnValue($this->resource));
+ $this->handler->expects($this->any())
+ ->method('streamSetTimeout')
+ ->will($this->returnValue(true));
+ $this->handler->expects($this->any())
+ ->method('closeSocket')
+ ->will($this->returnValue(true));
+ }
+}
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php
index f1feb2281..c6f5fac99 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php
@@ -191,6 +191,40 @@ class RotatingFileHandlerTest extends TestCase
);
}
+ /**
+ * @dataProvider rotationWhenSimilarFilesExistTests
+ */
+ public function testRotationWhenSimilarFileNamesExist($dateFormat)
+ {
+ touch($old1 = __DIR__.'/Fixtures/foo-foo-'.date($dateFormat).'.rot');
+ touch($old2 = __DIR__.'/Fixtures/foo-bar-'.date($dateFormat).'.rot');
+
+ $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot';
+
+ $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);
+ $handler->setFormatter($this->getIdentityFormatter());
+ $handler->setFilenameFormat('{filename}-{date}', $dateFormat);
+ $handler->handle($this->getRecord());
+ $handler->close();
+
+ $this->assertTrue(file_exists($log));
+ }
+
+ public function rotationWhenSimilarFilesExistTests()
+ {
+
+ return array(
+ 'Rotation is triggered when the file of the current day is not present but similar exists'
+ => array(RotatingFileHandler::FILE_PER_DAY),
+
+ 'Rotation is triggered when the file of the current month is not present but similar exists'
+ => array(RotatingFileHandler::FILE_PER_MONTH),
+
+ 'Rotation is triggered when the file of the current year is not present but similar exists'
+ => array(RotatingFileHandler::FILE_PER_YEAR),
+ );
+ }
+
public function testReuseCurrentFile()
{
$log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php
index e1aa96d71..b9de73679 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php
@@ -320,12 +320,12 @@ class SlackRecordTest extends TestCase
'short' => false,
),
array(
- 'title' => 'tags',
+ 'title' => 'Tags',
'value' => sprintf('```%s```', json_encode($extra['tags'])),
'short' => false
),
array(
- 'title' => 'test',
+ 'title' => 'Test',
'value' => $context['test'],
'short' => false
)
@@ -353,6 +353,14 @@ class SlackRecordTest extends TestCase
$this->assertSame($record['datetime']->getTimestamp(), $attachment['ts']);
}
+ public function testContextHasException()
+ {
+ $record = $this->getRecord(Logger::CRITICAL, 'This is a critical message.', array('exception' => new \Exception()));
+ $slackRecord = new SlackRecord(null, null, true, null, false, true);
+ $data = $slackRecord->getSlackData($record);
+ $this->assertInternalType('string', $data['attachments'][0]['fields'][1]['value']);
+ }
+
public function testExcludeExtraAndContextFields()
{
$record = $this->getRecord(
@@ -368,12 +376,12 @@ class SlackRecordTest extends TestCase
$expected = array(
array(
- 'title' => 'info',
+ 'title' => 'Info',
'value' => sprintf('```%s```', json_encode(array('author' => 'Jordi'), $this->jsonPrettyPrintFlag)),
'short' => false
),
array(
- 'title' => 'tags',
+ 'title' => 'Tags',
'value' => sprintf('```%s```', json_encode(array('web'))),
'short' => false
),
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php
index 1f9c1f287..1da987c9c 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php
@@ -77,6 +77,13 @@ class SocketHandlerTest extends TestCase
$this->assertEquals(10.25, $this->handler->getWritingTimeout());
}
+ public function testSetChunkSize()
+ {
+ $this->createHandler('localhost:1234');
+ $this->handler->setChunkSize(1025);
+ $this->assertEquals(1025, $this->handler->getChunkSize());
+ }
+
public function testSetConnectionString()
{
$this->createHandler('tcp://localhost:9090');
@@ -120,6 +127,19 @@ class SocketHandlerTest extends TestCase
$this->writeRecord('Hello world');
}
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testExceptionIsThrownIfCannotSetChunkSize()
+ {
+ $this->setMockHandler(array('streamSetChunkSize'));
+ $this->handler->setChunkSize(8192);
+ $this->handler->expects($this->once())
+ ->method('streamSetChunkSize')
+ ->will($this->returnValue(false));
+ $this->writeRecord('Hello world');
+ }
+
/**
* @expectedException RuntimeException
*/
@@ -304,6 +324,12 @@ class SocketHandlerTest extends TestCase
->will($this->returnValue(true));
}
+ if (!in_array('streamSetChunkSize', $methods)) {
+ $this->handler->expects($this->any())
+ ->method('streamSetChunkSize')
+ ->will($this->returnValue(8192));
+ }
+
$this->handler->setFormatter($this->getIdentityFormatter());
}
}
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php
index bfb8d3df2..a7c4fc986 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php
@@ -54,6 +54,52 @@ class TestHandlerTest extends TestCase
$this->assertEquals(array($record), $records);
}
+ public function testHandlerAssertEmptyContext() {
+ $handler = new TestHandler;
+ $record = $this->getRecord(Logger::WARNING, 'test', array());
+ $this->assertFalse($handler->hasWarning(array(
+ 'message' => 'test',
+ 'context' => array(),
+ )));
+
+ $handler->handle($record);
+
+ $this->assertTrue($handler->hasWarning(array(
+ 'message' => 'test',
+ 'context' => array(),
+ )));
+ $this->assertFalse($handler->hasWarning(array(
+ 'message' => 'test',
+ 'context' => array(
+ 'foo' => 'bar'
+ ),
+ )));
+ }
+
+ public function testHandlerAssertNonEmptyContext() {
+ $handler = new TestHandler;
+ $record = $this->getRecord(Logger::WARNING, 'test', array('foo' => 'bar'));
+ $this->assertFalse($handler->hasWarning(array(
+ 'message' => 'test',
+ 'context' => array(
+ 'foo' => 'bar'
+ ),
+ )));
+
+ $handler->handle($record);
+
+ $this->assertTrue($handler->hasWarning(array(
+ 'message' => 'test',
+ 'context' => array(
+ 'foo' => 'bar'
+ ),
+ )));
+ $this->assertFalse($handler->hasWarning(array(
+ 'message' => 'test',
+ 'context' => array(),
+ )));
+ }
+
public function methodProvider()
{
return array(
diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php
index 8d37a1fcc..0594a232b 100644
--- a/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php
@@ -87,6 +87,29 @@ class WhatFailureGroupHandlerTest extends TestCase
$this->assertTrue($records[0]['extra']['foo']);
}
+ /**
+ * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch
+ */
+ public function testHandleBatchUsesProcessors()
+ {
+ $testHandlers = array(new TestHandler(), new TestHandler());
+ $handler = new WhatFailureGroupHandler($testHandlers);
+ $handler->pushProcessor(function ($record) {
+ $record['extra']['foo'] = true;
+
+ return $record;
+ });
+ $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO)));
+ foreach ($testHandlers as $test) {
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertTrue(count($test->getRecords()) === 2);
+ $records = $test->getRecords();
+ $this->assertTrue($records[0]['extra']['foo']);
+ $this->assertTrue($records[1]['extra']['foo']);
+ }
+ }
+
/**
* @covers Monolog\Handler\WhatFailureGroupHandler::handle
*/
diff --git a/vendor/monolog/monolog/tests/Monolog/LoggerTest.php b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php
index 1ecc34a0a..442e87dea 100644
--- a/vendor/monolog/monolog/tests/Monolog/LoggerTest.php
+++ b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php
@@ -545,4 +545,146 @@ class LoggerTest extends \PHPUnit_Framework_TestCase
'without microseconds' => array(false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame'),
);
}
+
+ /**
+ * @covers Monolog\Logger::setExceptionHandler
+ */
+ public function testSetExceptionHandler()
+ {
+ $logger = new Logger(__METHOD__);
+ $this->assertNull($logger->getExceptionHandler());
+ $callback = function ($ex) {
+ };
+ $logger->setExceptionHandler($callback);
+ $this->assertEquals($callback, $logger->getExceptionHandler());
+ }
+
+ /**
+ * @covers Monolog\Logger::setExceptionHandler
+ * @expectedException InvalidArgumentException
+ */
+ public function testBadExceptionHandlerType()
+ {
+ $logger = new Logger(__METHOD__);
+ $logger->setExceptionHandler(false);
+ }
+
+ /**
+ * @covers Monolog\Logger::handleException
+ * @expectedException Exception
+ */
+ public function testDefaultHandleException()
+ {
+ $logger = new Logger(__METHOD__);
+ $handler = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler->expects($this->any())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+ $handler->expects($this->any())
+ ->method('handle')
+ ->will($this->throwException(new \Exception('Some handler exception')))
+ ;
+ $logger->pushHandler($handler);
+ $logger->info('test');
+ }
+
+ /**
+ * @covers Monolog\Logger::handleException
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testCustomHandleException()
+ {
+ $logger = new Logger(__METHOD__);
+ $that = $this;
+ $logger->setExceptionHandler(function ($e, $record) use ($that) {
+ $that->assertEquals($e->getMessage(), 'Some handler exception');
+ $that->assertTrue(is_array($record));
+ $that->assertEquals($record['message'], 'test');
+ });
+ $handler = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler->expects($this->any())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+ $handler->expects($this->any())
+ ->method('handle')
+ ->will($this->throwException(new \Exception('Some handler exception')))
+ ;
+ $logger->pushHandler($handler);
+ $logger->info('test');
+ }
+
+ public function testReset()
+ {
+ $logger = new Logger('app');
+
+ $testHandler = new Handler\TestHandler();
+ $bufferHandler = new Handler\BufferHandler($testHandler);
+ $groupHandler = new Handler\GroupHandler(array($bufferHandler));
+ $fingersCrossedHandler = new Handler\FingersCrossedHandler($groupHandler);
+
+ $logger->pushHandler($fingersCrossedHandler);
+
+ $processorUid1 = new Processor\UidProcessor(10);
+ $uid1 = $processorUid1->getUid();
+ $groupHandler->pushProcessor($processorUid1);
+
+ $processorUid2 = new Processor\UidProcessor(5);
+ $uid2 = $processorUid2->getUid();
+ $logger->pushProcessor($processorUid2);
+
+ $getProperty = function ($object, $property) {
+ $reflectionProperty = new \ReflectionProperty(get_class($object), $property);
+ $reflectionProperty->setAccessible(true);
+
+ return $reflectionProperty->getValue($object);
+ };
+ $that = $this;
+ $assertBufferOfBufferHandlerEmpty = function () use ($getProperty, $bufferHandler, $that) {
+ $that->assertEmpty($getProperty($bufferHandler, 'buffer'));
+ };
+ $assertBuffersEmpty = function() use ($assertBufferOfBufferHandlerEmpty, $getProperty, $fingersCrossedHandler, $that) {
+ $assertBufferOfBufferHandlerEmpty();
+ $that->assertEmpty($getProperty($fingersCrossedHandler, 'buffer'));
+ };
+
+ $logger->debug('debug');
+ $logger->reset();
+ $assertBuffersEmpty();
+ $this->assertFalse($testHandler->hasDebugRecords());
+ $this->assertFalse($testHandler->hasErrorRecords());
+ $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
+ $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
+
+ $logger->debug('debug');
+ $logger->error('error');
+ $logger->reset();
+ $assertBuffersEmpty();
+ $this->assertTrue($testHandler->hasDebugRecords());
+ $this->assertTrue($testHandler->hasErrorRecords());
+ $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
+ $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
+
+ $logger->info('info');
+ $this->assertNotEmpty($getProperty($fingersCrossedHandler, 'buffer'));
+ $assertBufferOfBufferHandlerEmpty();
+ $this->assertFalse($testHandler->hasInfoRecords());
+
+ $logger->reset();
+ $assertBuffersEmpty();
+ $this->assertFalse($testHandler->hasInfoRecords());
+ $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
+ $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
+
+ $logger->notice('notice');
+ $logger->emergency('emergency');
+ $logger->reset();
+ $assertBuffersEmpty();
+ $this->assertFalse($testHandler->hasInfoRecords());
+ $this->assertTrue($testHandler->hasNoticeRecords());
+ $this->assertTrue($testHandler->hasEmergencyRecords());
+ $this->assertNotSame($uid1, $processorUid1->getUid());
+ $this->assertNotSame($uid2, $processorUid2->getUid());
+ }
}
diff --git a/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php
new file mode 100644
index 000000000..9fa079290
--- /dev/null
+++ b/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php
@@ -0,0 +1,287 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+use Monolog\Handler\StreamHandler;
+use Monolog\Handler\TestHandler;
+use Psr\Log\LogLevel;
+
+/**
+ * @author Robert Gust-Bardon
+ * @covers Monolog\SignalHandler
+ */
+class SignalHandlerTest extends TestCase
+{
+
+ private $asyncSignalHandling;
+ private $blockedSignals;
+ private $signalHandlers;
+
+ protected function setUp()
+ {
+ $this->signalHandlers = array();
+ if (extension_loaded('pcntl')) {
+ if (function_exists('pcntl_async_signals')) {
+ $this->asyncSignalHandling = pcntl_async_signals();
+ }
+ if (function_exists('pcntl_sigprocmask')) {
+ pcntl_sigprocmask(SIG_BLOCK, array(), $this->blockedSignals);
+ }
+ }
+ }
+
+ protected function tearDown()
+ {
+ if ($this->asyncSignalHandling !== null) {
+ pcntl_async_signals($this->asyncSignalHandling);
+ }
+ if ($this->blockedSignals !== null) {
+ pcntl_sigprocmask(SIG_SETMASK, $this->blockedSignals);
+ }
+ if ($this->signalHandlers) {
+ pcntl_signal_dispatch();
+ foreach ($this->signalHandlers as $signo => $handler) {
+ pcntl_signal($signo, $handler);
+ }
+ }
+ }
+
+ private function setSignalHandler($signo, $handler = SIG_DFL) {
+ if (function_exists('pcntl_signal_get_handler')) {
+ $this->signalHandlers[$signo] = pcntl_signal_get_handler($signo);
+ } else {
+ $this->signalHandlers[$signo] = SIG_DFL;
+ }
+ $this->assertTrue(pcntl_signal($signo, $handler));
+ }
+
+ public function testHandleSignal()
+ {
+ $logger = new Logger('test', array($handler = new TestHandler));
+ $errHandler = new SignalHandler($logger);
+ $signo = 2; // SIGINT.
+ $siginfo = array('signo' => $signo, 'errno' => 0, 'code' => 0);
+ $errHandler->handleSignal($signo, $siginfo);
+ $this->assertCount(1, $handler->getRecords());
+ $this->assertTrue($handler->hasCriticalRecords());
+ $records = $handler->getRecords();
+ $this->assertSame($siginfo, $records[0]['context']);
+ }
+
+ /**
+ * @depends testHandleSignal
+ * @requires extension pcntl
+ * @requires extension posix
+ * @requires function pcntl_signal
+ * @requires function pcntl_signal_dispatch
+ * @requires function posix_getpid
+ * @requires function posix_kill
+ */
+ public function testRegisterSignalHandler()
+ {
+ // SIGCONT and SIGURG should be ignored by default.
+ if (!defined('SIGCONT') || !defined('SIGURG')) {
+ $this->markTestSkipped('This test requires the SIGCONT and SIGURG pcntl constants.');
+ }
+
+ $this->setSignalHandler(SIGCONT, SIG_IGN);
+ $this->setSignalHandler(SIGURG, SIG_IGN);
+
+ $logger = new Logger('test', array($handler = new TestHandler));
+ $errHandler = new SignalHandler($logger);
+ $pid = posix_getpid();
+
+ $this->assertTrue(posix_kill($pid, SIGURG));
+ $this->assertTrue(pcntl_signal_dispatch());
+ $this->assertCount(0, $handler->getRecords());
+
+ $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, false);
+
+ $this->assertTrue(posix_kill($pid, SIGCONT));
+ $this->assertTrue(pcntl_signal_dispatch());
+ $this->assertCount(0, $handler->getRecords());
+
+ $this->assertTrue(posix_kill($pid, SIGURG));
+ $this->assertTrue(pcntl_signal_dispatch());
+ $this->assertCount(1, $handler->getRecords());
+ $this->assertTrue($handler->hasInfoThatContains('SIGURG'));
+ }
+
+ /**
+ * @dataProvider defaultPreviousProvider
+ * @depends testRegisterSignalHandler
+ * @requires function pcntl_fork
+ * @requires function pcntl_sigprocmask
+ * @requires function pcntl_waitpid
+ */
+ public function testRegisterDefaultPreviousSignalHandler($signo, $callPrevious, $expected)
+ {
+ $this->setSignalHandler($signo, SIG_DFL);
+
+ $path = tempnam(sys_get_temp_dir(), 'monolog-');
+ $this->assertNotFalse($path);
+
+ $pid = pcntl_fork();
+ if ($pid === 0) { // Child.
+ $streamHandler = new StreamHandler($path);
+ $streamHandler->setFormatter($this->getIdentityFormatter());
+ $logger = new Logger('test', array($streamHandler));
+ $errHandler = new SignalHandler($logger);
+ $errHandler->registerSignalHandler($signo, LogLevel::INFO, $callPrevious, false, false);
+ pcntl_sigprocmask(SIG_SETMASK, array(SIGCONT));
+ posix_kill(posix_getpid(), $signo);
+ pcntl_signal_dispatch();
+ // If $callPrevious is true, SIGINT should terminate by this line.
+ pcntl_sigprocmask(SIG_BLOCK, array(), $oldset);
+ file_put_contents($path, implode(' ', $oldset), FILE_APPEND);
+ posix_kill(posix_getpid(), $signo);
+ pcntl_signal_dispatch();
+ exit();
+ }
+
+ $this->assertNotSame(-1, $pid);
+ $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
+ $this->assertNotSame(-1, $status);
+ $this->assertSame($expected, file_get_contents($path));
+ }
+
+ public function defaultPreviousProvider()
+ {
+ if (!defined('SIGCONT') || !defined('SIGINT') || !defined('SIGURG')) {
+ return array();
+ }
+
+ return array(
+ array(SIGINT, false, 'Program received signal SIGINT'.SIGCONT.'Program received signal SIGINT'),
+ array(SIGINT, true, 'Program received signal SIGINT'),
+ array(SIGURG, false, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'),
+ array(SIGURG, true, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'),
+ );
+ }
+
+ /**
+ * @dataProvider callablePreviousProvider
+ * @depends testRegisterSignalHandler
+ * @requires function pcntl_signal_get_handler
+ */
+ public function testRegisterCallablePreviousSignalHandler($callPrevious)
+ {
+ $this->setSignalHandler(SIGURG, SIG_IGN);
+
+ $logger = new Logger('test', array($handler = new TestHandler));
+ $errHandler = new SignalHandler($logger);
+ $previousCalled = 0;
+ pcntl_signal(SIGURG, function ($signo, array $siginfo = null) use (&$previousCalled) {
+ ++$previousCalled;
+ });
+ $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, $callPrevious, false, false);
+ $this->assertTrue(posix_kill(posix_getpid(), SIGURG));
+ $this->assertTrue(pcntl_signal_dispatch());
+ $this->assertCount(1, $handler->getRecords());
+ $this->assertTrue($handler->hasInfoThatContains('SIGURG'));
+ $this->assertSame($callPrevious ? 1 : 0, $previousCalled);
+ }
+
+ public function callablePreviousProvider()
+ {
+ return array(
+ array(false),
+ array(true),
+ );
+ }
+
+ /**
+ * @dataProvider restartSyscallsProvider
+ * @depends testRegisterDefaultPreviousSignalHandler
+ * @requires function pcntl_fork
+ * @requires function pcntl_waitpid
+ */
+ public function testRegisterSyscallRestartingSignalHandler($restartSyscalls)
+ {
+ $this->setSignalHandler(SIGURG, SIG_IGN);
+
+ $parentPid = posix_getpid();
+ $microtime = microtime(true);
+
+ $pid = pcntl_fork();
+ if ($pid === 0) { // Child.
+ usleep(100000);
+ posix_kill($parentPid, SIGURG);
+ usleep(100000);
+ exit();
+ }
+
+ $this->assertNotSame(-1, $pid);
+ $logger = new Logger('test', array($handler = new TestHandler));
+ $errHandler = new SignalHandler($logger);
+ $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, $restartSyscalls, false);
+ if ($restartSyscalls) {
+ // pcntl_wait is expected to be restarted after the signal handler.
+ $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
+ } else {
+ // pcntl_wait is expected to be interrupted when the signal handler is invoked.
+ $this->assertSame(-1, pcntl_waitpid($pid, $status));
+ }
+ $this->assertSame($restartSyscalls, microtime(true) - $microtime > 0.15);
+ $this->assertTrue(pcntl_signal_dispatch());
+ $this->assertCount(1, $handler->getRecords());
+ if ($restartSyscalls) {
+ // The child has already exited.
+ $this->assertSame(-1, pcntl_waitpid($pid, $status));
+ } else {
+ // The child has not exited yet.
+ $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
+ }
+ }
+
+ public function restartSyscallsProvider()
+ {
+ return array(
+ array(false),
+ array(true),
+ array(false),
+ array(true),
+ );
+ }
+
+ /**
+ * @dataProvider asyncProvider
+ * @depends testRegisterDefaultPreviousSignalHandler
+ * @requires function pcntl_async_signals
+ */
+ public function testRegisterAsyncSignalHandler($initialAsync, $desiredAsync, $expectedBefore, $expectedAfter)
+ {
+ $this->setSignalHandler(SIGURG, SIG_IGN);
+ pcntl_async_signals($initialAsync);
+
+ $logger = new Logger('test', array($handler = new TestHandler));
+ $errHandler = new SignalHandler($logger);
+ $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, $desiredAsync);
+ $this->assertTrue(posix_kill(posix_getpid(), SIGURG));
+ $this->assertCount($expectedBefore, $handler->getRecords());
+ $this->assertTrue(pcntl_signal_dispatch());
+ $this->assertCount($expectedAfter, $handler->getRecords());
+ }
+
+ public function asyncProvider()
+ {
+ return array(
+ array(false, false, 0, 1),
+ array(false, null, 0, 1),
+ array(false, true, 1, 1),
+ array(true, false, 0, 1),
+ array(true, null, 1, 1),
+ array(true, true, 1, 1),
+ );
+ }
+
+}
diff --git a/vendor/nesbot/carbon/composer.json b/vendor/nesbot/carbon/composer.json
index 40be3f55f..709ff4ee7 100644
--- a/vendor/nesbot/carbon/composer.json
+++ b/vendor/nesbot/carbon/composer.json
@@ -25,9 +25,12 @@
"symfony/translation": "~2.6 || ~3.0 || ~4.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "~2",
"phpunit/phpunit": "^4.8.35 || ^5.7"
},
+ "suggest": {
+ "friendsofphp/php-cs-fixer": "Needed for the `composer phpcs` command. Allow to automatically fix code style.",
+ "phpstan/phpstan": "Needed for the `composer phpstan` command. Allow to detect potential errors."
+ },
"autoload": {
"psr-4": {
"": "src/"
diff --git a/vendor/nesbot/carbon/src/Carbon/Carbon.php b/vendor/nesbot/carbon/src/Carbon/Carbon.php
index f62dd45ec..a81770d10 100644
--- a/vendor/nesbot/carbon/src/Carbon/Carbon.php
+++ b/vendor/nesbot/carbon/src/Carbon/Carbon.php
@@ -13,6 +13,7 @@ namespace Carbon;
use Carbon\Exceptions\InvalidDateException;
use Closure;
+use DateInterval;
use DatePeriod;
use DateTime;
use DateTimeInterface;
@@ -195,7 +196,7 @@ class Carbon extends DateTime implements JsonSerializable
't' => '(2[89]|3[01])',
'L' => '(0|1)',
'o' => '([1-9][0-9]{0,4})',
- 'Y' => '([1-9][0-9]{0,4})',
+ 'Y' => '([1-9]?[0-9]{4})',
'y' => '([0-9]{2})',
'a' => '(am|pm)',
'A' => '(AM|PM)',
@@ -217,8 +218,8 @@ class Carbon extends DateTime implements JsonSerializable
'U' => '([0-9]*)',
// The formats below are combinations of the above formats.
- 'c' => '(([1-9][0-9]{0,4})\-(1[012]|0[1-9])\-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[\+\-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP
- 'r' => '(([a-zA-Z]{3}), ([123][0-9]|[1-9]) ([a-zA-Z]{3}) ([1-9][0-9]{0,4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [\+\-](1[012]|0[0-9])([0134][05]))', // D, j M Y H:i:s O
+ 'c' => '(([1-9]?[0-9]{4})\-(1[012]|0[1-9])\-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[\+\-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP
+ 'r' => '(([a-zA-Z]{3}), ([123][0-9]|[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [\+\-](1[012]|0[0-9])([0134][05]))', // D, j M Y H:i:s O
);
/**
@@ -2264,6 +2265,13 @@ class Carbon extends DateTime implements JsonSerializable
return $this->gt($date1) && $this->lt($date2);
}
+ protected function floatDiffInSeconds($date)
+ {
+ $date = $this->resolveCarbon($date);
+
+ return abs($this->diffInRealSeconds($date, false) + ($date->micro - $this->micro) / 1000000);
+ }
+
/**
* Get the closest date from the instance.
*
@@ -2274,7 +2282,7 @@ class Carbon extends DateTime implements JsonSerializable
*/
public function closest($date1, $date2)
{
- return $this->diffInSeconds($date1) < $this->diffInSeconds($date2) ? $date1 : $date2;
+ return $this->floatDiffInSeconds($date1) < $this->floatDiffInSeconds($date2) ? $date1 : $date2;
}
/**
@@ -2287,7 +2295,7 @@ class Carbon extends DateTime implements JsonSerializable
*/
public function farthest($date1, $date2)
{
- return $this->diffInSeconds($date1) > $this->diffInSeconds($date2) ? $date1 : $date2;
+ return $this->floatDiffInSeconds($date1) > $this->floatDiffInSeconds($date2) ? $date1 : $date2;
}
/**
@@ -3754,16 +3762,91 @@ class Carbon extends DateTime implements JsonSerializable
///////////////////////////////////////////////////////////////////
/**
- * Get the difference as a CarbonInterval instance
- *
- * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
- * @param bool $absolute Get the absolute of the difference
+ * @param DateInterval $diff
+ * @param bool $absolute
+ * @param bool $trimMicroseconds
*
* @return CarbonInterval
*/
- public function diffAsCarbonInterval($date = null, $absolute = true)
+ protected static function fixDiffInterval(DateInterval $diff, $absolute, $trimMicroseconds)
{
- return CarbonInterval::instance($this->diff($this->resolveCarbon($date), $absolute));
+ $diff = CarbonInterval::instance($diff, $trimMicroseconds);
+
+ // @codeCoverageIgnoreStart
+ if (version_compare(PHP_VERSION, '7.1.0-dev', '<')) {
+ return $diff;
+ }
+
+ // Work-around for https://bugs.php.net/bug.php?id=77145
+ if ($diff->f > 0 && $diff->y === -1 && $diff->m === 11 && $diff->d >= 27 && $diff->h === 23 && $diff->i === 59 && $diff->s === 59) {
+ $diff->y = 0;
+ $diff->m = 0;
+ $diff->d = 0;
+ $diff->h = 0;
+ $diff->i = 0;
+ $diff->s = 0;
+ $diff->f = (1000000 - round($diff->f * 1000000)) / 1000000;
+ $diff->invert();
+ } elseif ($diff->f < 0) {
+ if ($diff->s !== 0 || $diff->i !== 0 || $diff->h !== 0 || $diff->d !== 0 || $diff->m !== 0 || $diff->y !== 0) {
+ $diff->f = (round($diff->f * 1000000) + 1000000) / 1000000;
+ $diff->s--;
+ if ($diff->s < 0) {
+ $diff->s += 60;
+ $diff->i--;
+ if ($diff->i < 0) {
+ $diff->i += 60;
+ $diff->h--;
+ if ($diff->h < 0) {
+ $diff->i += 24;
+ $diff->d--;
+ if ($diff->d < 0) {
+ $diff->d += 30;
+ $diff->m--;
+ if ($diff->m < 0) {
+ $diff->m += 12;
+ $diff->y--;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ $diff->f *= -1;
+ $diff->invert();
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ if ($absolute && $diff->invert) {
+ $diff->invert();
+ }
+
+ return $diff;
+ }
+
+ /**
+ * Get the difference as a CarbonInterval instance.
+ *
+ * Pass false as second argument to get a microseconds-precise interval. Else
+ * microseconds in the original interval will not be kept.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ * @param bool $trimMicroseconds (true by default)
+ *
+ * @return CarbonInterval
+ */
+ public function diffAsCarbonInterval($date = null, $absolute = true, $trimMicroseconds = true)
+ {
+ $from = $this;
+ $to = $this->resolveCarbon($date);
+
+ if ($trimMicroseconds) {
+ $from = $from->copy()->startOfSecond();
+ $to = $to->copy()->startOfSecond();
+ }
+
+ return static::fixDiffInterval($from->diff($to, $absolute), $absolute, $trimMicroseconds);
}
/**
@@ -3973,6 +4056,9 @@ class Carbon extends DateTime implements JsonSerializable
public function diffInSeconds($date = null, $absolute = true)
{
$diff = $this->diff($this->resolveCarbon($date));
+ if (!$diff->days && version_compare(PHP_VERSION, '5.4.0-dev', '>=')) {
+ $diff = static::fixDiffInterval($diff, $absolute, false);
+ }
$value = $diff->days * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE +
$diff->h * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE +
$diff->i * static::SECONDS_PER_MINUTE +
@@ -4651,7 +4737,22 @@ class Carbon extends DateTime implements JsonSerializable
*/
public function average($date = null)
{
- return $this->addSeconds((int) ($this->diffInSeconds($this->resolveCarbon($date), false) / 2));
+ $date = $this->resolveCarbon($date);
+ $increment = $this->diffInRealSeconds($date, false) / 2;
+ $intIncrement = floor($increment);
+ $microIncrement = (int) (($date->micro - $this->micro) / 2 + 1000000 * ($increment - $intIncrement));
+ $micro = (int) ($this->micro + $microIncrement);
+ while ($micro >= 1000000) {
+ $micro -= 1000000;
+ $intIncrement++;
+ }
+ $this->addSeconds($intIncrement);
+
+ if (version_compare(PHP_VERSION, '7.1.8-dev', '>=')) {
+ $this->setTime($this->hour, $this->minute, $this->second, $micro);
+ }
+
+ return $this;
}
///////////////////////////////////////////////////////////////////
diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php b/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php
index a4d785005..0bd9ab9df 100644
--- a/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php
+++ b/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php
@@ -490,14 +490,27 @@ class CarbonInterval extends DateInterval
* DateInterval objects created from DateTime::diff() as you can't externally
* set the $days field.
*
+ * Pass false as second argument to get a microseconds-precise interval. Else
+ * microseconds in the original interval will not be kept.
+ *
* @param DateInterval $di
+ * @param bool $trimMicroseconds (true by default)
*
* @return static
*/
- public static function instance(DateInterval $di)
+ public static function instance(DateInterval $di, $trimMicroseconds = true)
{
+ $microseconds = $trimMicroseconds || version_compare(PHP_VERSION, '7.1.0-dev', '<') ? 0 : $di->f;
$instance = new static(static::getDateIntervalSpec($di));
+ if ($microseconds) {
+ $instance->f = $microseconds;
+ }
$instance->invert = $di->invert;
+ foreach (array('y', 'm', 'd', 'h', 'i', 's') as $unit) {
+ if ($di->$unit < 0) {
+ $instance->$unit *= -1;
+ }
+ }
return $instance;
}
@@ -911,7 +924,7 @@ class CarbonInterval extends DateInterval
*/
public function add(DateInterval $interval)
{
- $sign = $interval->invert === 1 ? -1 : 1;
+ $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1;
if (static::wasCreatedFromDiff($interval)) {
$this->dayz += $interval->days * $sign;
@@ -924,6 +937,18 @@ class CarbonInterval extends DateInterval
$this->seconds += $interval->s * $sign;
}
+ if (($this->years || $this->months || $this->dayz || $this->hours || $this->minutes || $this->seconds) &&
+ $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0
+ ) {
+ $this->years *= -1;
+ $this->months *= -1;
+ $this->dayz *= -1;
+ $this->hours *= -1;
+ $this->minutes *= -1;
+ $this->seconds *= -1;
+ $this->invert();
+ }
+
return $this;
}
@@ -961,15 +986,15 @@ class CarbonInterval extends DateInterval
public static function getDateIntervalSpec(DateInterval $interval)
{
$date = array_filter(array(
- static::PERIOD_YEARS => $interval->y,
- static::PERIOD_MONTHS => $interval->m,
- static::PERIOD_DAYS => $interval->d,
+ static::PERIOD_YEARS => abs($interval->y),
+ static::PERIOD_MONTHS => abs($interval->m),
+ static::PERIOD_DAYS => abs($interval->d),
));
$time = array_filter(array(
- static::PERIOD_HOURS => $interval->h,
- static::PERIOD_MINUTES => $interval->i,
- static::PERIOD_SECONDS => $interval->s,
+ static::PERIOD_HOURS => abs($interval->h),
+ static::PERIOD_MINUTES => abs($interval->i),
+ static::PERIOD_SECONDS => abs($interval->s),
));
$specString = static::PERIOD_PREFIX;
diff --git a/vendor/nexmo/client/LICENSE.txt b/vendor/nexmo/client/LICENSE.txt
new file mode 100644
index 000000000..ac86e75d8
--- /dev/null
+++ b/vendor/nexmo/client/LICENSE.txt
@@ -0,0 +1,15 @@
+The MIT License (MIT)
+Copyright (c) 2016-2018 Nexmo, Inc
+
+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/vendor/nexmo/client/composer.json b/vendor/nexmo/client/composer.json
new file mode 100644
index 000000000..4eb1d1aaf
--- /dev/null
+++ b/vendor/nexmo/client/composer.json
@@ -0,0 +1,41 @@
+{
+ "name": "nexmo/client",
+ "description": "PHP Client for using Nexmo's API.",
+ "license": "MIT",
+ "type": "library",
+ "authors": [
+ {
+ "name": "Tim Lytle",
+ "email": "tim@nexmo.com",
+ "role": "Developer",
+ "homepage": "http://twitter.com/tjlytle"
+ }
+ ],
+ "support": {
+ "email": "devrel@nexmo.com"
+ },
+ "require": {
+ "php": ">=5.6",
+ "php-http/client-implementation": "^1.0",
+ "zendframework/zend-diactoros": "^1.3",
+ "php-http/guzzle6-adapter": "^1.0",
+ "lcobucci/jwt": "^3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7",
+ "php-http/mock-client": "^0.3.0",
+ "estahn/phpunit-json-assertions": "^1.0.0",
+ "squizlabs/php_codesniffer": "^3.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "Nexmo\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Nexmo\\": "test/",
+ "NexmoTest\\": "test/"
+ }
+ }
+}
diff --git a/vendor/nexmo/client/examples/fetch_recording.php b/vendor/nexmo/client/examples/fetch_recording.php
new file mode 100644
index 000000000..bddde2c4b
--- /dev/null
+++ b/vendor/nexmo/client/examples/fetch_recording.php
@@ -0,0 +1,12 @@
+get($recording);
+
+file_put_contents($recordingId.'.mp3', $data->getBody());
diff --git a/vendor/nexmo/client/examples/send.php b/vendor/nexmo/client/examples/send.php
new file mode 100644
index 000000000..90787fa52
--- /dev/null
+++ b/vendor/nexmo/client/examples/send.php
@@ -0,0 +1,72 @@
+message()->send([
+ 'to' => NEXMO_TO,
+ 'from' => NEXMO_FROM,
+ 'text' => 'Test message from the Nexmo PHP Client'
+]);
+
+//array access provides response data
+echo "Sent message to " . $message['to'] . ". Balance is now " . $message['remaining-balance'] . PHP_EOL;
+
+sleep(1);
+
+//send message using object support
+$text = new \Nexmo\Message\Text(NEXMO_TO, NEXMO_FROM, 'Test message using PHP client library');
+$text->setClientRef('test-message')
+ ->setClass(\Nexmo\Message\Text::CLASS_FLASH);
+
+$client->message()->send($text);
+
+//method access
+echo "Sent message to " . $text->getTo() . ". Balance is now " . $text->getRemainingBalance() . PHP_EOL;
+
+sleep(1);
+
+//sending a message over 160 characters
+$longwinded = <<message()->send($text);
+
+echo "Sent message to " . $text->getTo() . ". Balance is now " . $text->getRemainingBalance() . PHP_EOL;
+echo "Message was split into " . count($text) . " messages, those message ids are: " . PHP_EOL;
+for($i = 0; $i < count($text); $i++){
+ echo $text[$i]['message-id'] . PHP_EOL;
+}
+
+echo "The account balance after each message was: " . PHP_EOL;
+for($i = 0; $i < count($text); $i++){
+ echo $text->getRemainingBalance($i) . PHP_EOL;
+}
+
+//easier iteration, can use methods or array access
+foreach($text as $index => $data){
+ echo "Balance was " . $text->getRemainingBalance($index) . " after message " . $data['message-id'] . " was sent." . PHP_EOL;
+}
+
+//an invalid request
+try{
+ $text = new \Nexmo\Message\Text('not valid', NEXMO_FROM, $longwinded);
+ $client->message()->send($text);
+} catch (Nexmo\Client\Exception\Request $e) {
+ //can still get the API response
+ $text = $e->getEntity();
+ $request = $text->getRequest(); //PSR-7 Request Object
+ $response = $text->getResponse(); //PSR-7 Response Object
+ $data = $text->getResponseData(); //parsed response object
+ $code = $e->getCode(); //nexmo error code
+ error_log($e->getMessage()); //nexmo error message
+}
diff --git a/vendor/nexmo/client/phpcs.xml b/vendor/nexmo/client/phpcs.xml
new file mode 100644
index 000000000..4dc7a1b75
--- /dev/null
+++ b/vendor/nexmo/client/phpcs.xml
@@ -0,0 +1,5 @@
+
+
+
+ ./src
+
diff --git a/vendor/nexmo/client/src/Account/Balance.php b/vendor/nexmo/client/src/Account/Balance.php
new file mode 100644
index 000000000..7894ad6c3
--- /dev/null
+++ b/vendor/nexmo/client/src/Account/Balance.php
@@ -0,0 +1,59 @@
+data['balance'] = $balance;
+ $this->data['auto_reload'] = $autoReload;
+ }
+
+ public function getBalance()
+ {
+ return $this['balance'];
+ }
+
+ public function getAutoReload()
+ {
+ return $this['auto_reload'];
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ $this->data = [
+ 'balance' => $json['value'],
+ 'auto_reload' => $json['autoReload']
+ ];
+ }
+
+ function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new Exception('Balance is read only');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new Exception('Balance is read only');
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Account/Client.php b/vendor/nexmo/client/src/Account/Client.php
new file mode 100644
index 000000000..d520c2884
--- /dev/null
+++ b/vendor/nexmo/client/src/Account/Client.php
@@ -0,0 +1,228 @@
+ $prefix
+ ]);
+
+ $request = new Request(
+ $this->getClient()->getRestUrl() . '/account/get-prefix-pricing/outbound?'.$queryString,
+ 'GET',
+ 'php://temp'
+ );
+
+ $response = $this->client->send($request);
+ $rawBody = $response->getBody()->getContents();
+
+ $body = json_decode($rawBody, true);
+
+ $codeCategory = (int) ($response->getStatusCode()/100);
+ if ($codeCategory != 2) {
+ if ($codeCategory == 4) {
+ throw new Exception\Request($body['error-code-label']);
+ }else if ($codeCategory == 5) {
+ throw new Exception\Server($body['error-code-label']);
+ }
+ }
+
+ if ($body['count'] == 0) {
+ return [];
+ }
+
+ // Multiple countries can match each prefix
+ $prices = [];
+
+ foreach ($body['prices'] as $p) {
+ $prefixPrice = new PrefixPrice();
+ $prefixPrice->jsonUnserialize($p);
+ $prices[] = $prefixPrice;
+ }
+ return $prices;
+ }
+
+ public function getSmsPrice($country)
+ {
+ $body = $this->makePricingRequest($country, 'sms');
+ $smsPrice = new SmsPrice();
+ $smsPrice->jsonUnserialize($body);
+ return $smsPrice;
+ }
+
+ public function getVoicePrice($country)
+ {
+ $body = $this->makePricingRequest($country, 'voice');
+ $voicePrice = new VoicePrice();
+ $voicePrice->jsonUnserialize($body);
+ return $voicePrice;
+ }
+
+ protected function makePricingRequest($country, $pricingType)
+ {
+ $queryString = http_build_query([
+ 'country' => $country
+ ]);
+
+ $request = new Request(
+ $this->getClient()->getRestUrl() . '/account/get-pricing/outbound/'.$pricingType.'?'.$queryString,
+ 'GET',
+ 'php://temp'
+ );
+
+ $response = $this->client->send($request);
+ $rawBody = $response->getBody()->getContents();
+
+ if ($rawBody === '') {
+ throw new Exception\Server('No results found');
+ }
+
+ return json_decode($rawBody, true);
+ }
+
+ public function getBalance()
+ {
+
+ $request = new Request(
+ $this->getClient()->getRestUrl() . '/account/get-balance',
+ 'GET',
+ 'php://temp'
+ );
+
+ $response = $this->client->send($request);
+ $rawBody = $response->getBody()->getContents();
+
+ if ($rawBody === '') {
+ throw new Exception\Server('No results found');
+ }
+
+ $body = json_decode($rawBody, true);
+
+ $balance = new Balance($body['value'], $body['autoReload']);
+ return $balance;
+ }
+
+ public function topUp($trx)
+ {
+ $body = [
+ 'trx' => $trx
+ ];
+
+ $request = new Request(
+ $this->getClient()->getRestUrl() . '/account/top-up'
+ ,'POST'
+ , 'php://temp'
+ , ['content-type' => 'application/x-www-form-urlencoded']
+ );
+
+ $request->getBody()->write(http_build_query($body));
+ $response = $this->client->send($request);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+ }
+
+ public function listSecrets($apiKey)
+ {
+ $body = $this->get( $this->getClient()->getApiUrl() . '/accounts/'.$apiKey.'/secrets');
+ return SecretCollection::fromApi($body);
+ }
+
+ public function getSecret($apiKey, $secretId)
+ {
+ $body = $this->get( $this->getClient()->getApiUrl() . '/accounts/'.$apiKey.'/secrets/'. $secretId);
+ return Secret::fromApi($body);
+ }
+
+ public function createSecret($apiKey, $newSecret)
+ {
+ $body = [
+ 'secret' => $newSecret
+ ];
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . '/accounts/'.$apiKey.'/secrets'
+ ,'POST'
+ , 'php://temp'
+ , ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($body));
+ $response = $this->client->send($request);
+
+ $rawBody = $response->getBody()->getContents();
+ $responseBody = json_decode($rawBody, true);
+ ApiErrorHandler::check($responseBody, $response->getStatusCode());
+
+ return Secret::fromApi($responseBody);
+ }
+
+ public function deleteSecret($apiKey, $secretId)
+ {
+ $request = new Request(
+ $this->getClient()->getApiUrl() . '/accounts/'.$apiKey.'/secrets/'. $secretId
+ ,'DELETE'
+ , 'php://temp'
+ , ['content-type' => 'application/json']
+ );
+
+ $response = $this->client->send($request);
+ $rawBody = $response->getBody()->getContents();
+ $body = json_decode($rawBody, true);
+
+ // This will throw an exception on any error
+ ApiErrorHandler::check($body, $response->getStatusCode());
+
+ // This returns a 204, so no response body
+ }
+
+ protected function get($url) {
+ $request = new Request(
+ $url
+ ,'GET'
+ , 'php://temp'
+ , ['content-type' => 'application/json']
+ );
+
+ $response = $this->client->send($request);
+ $rawBody = $response->getBody()->getContents();
+ $body = json_decode($rawBody, true);
+
+ // This will throw an exception on any error
+ ApiErrorHandler::check($body, $response->getStatusCode());
+
+ return $body;
+ }
+
+ protected function getException(ResponseInterface $response, $application = null)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error_title'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error_title'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ }
+
+ return $e;
+ }
+
+}
diff --git a/vendor/nexmo/client/src/Account/PrefixPrice.php b/vendor/nexmo/client/src/Account/PrefixPrice.php
new file mode 100644
index 000000000..9da71e60f
--- /dev/null
+++ b/vendor/nexmo/client/src/Account/PrefixPrice.php
@@ -0,0 +1,13 @@
+getNetworks();
+ if (isset($networks[$networkCode]))
+ {
+ return $networks[$networkCode]->{$this->priceMethod}();
+ }
+
+ return $this->getDefaultPrice();
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ // Convert CamelCase to snake_case as that's how we use array access in every other object
+ $data = [];
+ foreach ($json as $k => $v){
+ $k = ltrim(strtolower(preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', $k)), '_');
+
+ // PrefixPrice fixes
+ if ($k == 'country') {
+ $k = 'country_code';
+ }
+
+ if ($k == 'name') {
+ $data['country_display_name'] = $v;
+ $data['country_name'] = $v;
+ }
+
+ if ($k == 'prefix') {
+ $k = 'dialing_prefix';
+ }
+
+ $data[$k] = $v;
+ }
+
+ // Create objects for all the nested networks too
+ $networks = [];
+ if (isset($json['networks'])) {
+ foreach ($json['networks'] as $n){
+ if (isset($n['code'])) {
+ $n['networkCode'] = $n['code'];
+ unset ($n['code']);
+ }
+
+ if (isset($n['network'])) {
+ $n['networkName'] = $n['network'];
+ unset ($n['network']);
+ }
+
+ $network = new Network($n['networkCode'], $n['networkName']);
+ $network->jsonUnserialize($n);
+ $networks[$network->getCode()] = $network;
+ }
+ }
+
+ $data['networks'] = $networks;
+ $this->data = $data;
+ }
+
+ function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new Exception('Price is read only');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new Exception('Price is read only');
+ }
+}
diff --git a/vendor/nexmo/client/src/Account/Secret.php b/vendor/nexmo/client/src/Account/Secret.php
new file mode 100644
index 000000000..5566c6573
--- /dev/null
+++ b/vendor/nexmo/client/src/Account/Secret.php
@@ -0,0 +1,56 @@
+data = $data;
+ }
+
+ public function getId() {
+ return $this['id'];
+ }
+
+ public function getCreatedAt() {
+ return $this['created_at'];
+ }
+
+ public function getLinks() {
+ return $this['_links'];
+ }
+
+ public static function fromApi($data) {
+ if (!isset($data['id'])) {
+ throw new InvalidResponseException("Missing key: 'id");
+ }
+ if (!isset($data['created_at'])) {
+ throw new InvalidResponseException("Missing key: 'created_at");
+ }
+ return new self($data);
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new \Exception('Secret::offsetSet is not implemented');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new \Exception('Secret::offsetUnset is not implemented');
+ }
+
+}
diff --git a/vendor/nexmo/client/src/Account/SecretCollection.php b/vendor/nexmo/client/src/Account/SecretCollection.php
new file mode 100644
index 000000000..51aa11fec
--- /dev/null
+++ b/vendor/nexmo/client/src/Account/SecretCollection.php
@@ -0,0 +1,51 @@
+data = [
+ 'secrets' => $secrets,
+ '_links' => $links
+ ];
+ }
+
+ public function getSecrets() {
+ return $this['secrets'];
+ }
+
+ public function getLinks() {
+ return $this['_links'];
+ }
+
+ public static function fromApi($data) {
+ $secrets = [];
+ foreach ($data['_embedded']['secrets'] as $s) {
+ $secrets[] = Secret::fromApi($s);
+ }
+ return new self($secrets, $data['_links']);
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new \Exception('SecretCollection::offsetSet is not implemented');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new \Exception('SecretCollection::offsetUnset is not implemented');
+ }
+
+}
diff --git a/vendor/nexmo/client/src/Account/SmsPrice.php b/vendor/nexmo/client/src/Account/SmsPrice.php
new file mode 100644
index 000000000..a5532be4e
--- /dev/null
+++ b/vendor/nexmo/client/src/Account/SmsPrice.php
@@ -0,0 +1,7 @@
+id = $id;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setVoiceConfig(VoiceConfig $config)
+ {
+ $this->voiceConfig = $config;
+ return $this;
+ }
+
+ /**
+ * @return VoiceConfig
+ */
+ public function getVoiceConfig()
+ {
+ if(!isset($this->voiceConfig)){
+ $this->setVoiceConfig(new VoiceConfig());
+ $data = $this->getResponseData();
+ if(isset($data['voice']) AND isset($data['voice']['webhooks'])){
+ foreach($data['voice']['webhooks'] as $webhook){
+ $this->voiceConfig->setWebhook($webhook['endpoint_type'], $webhook['endpoint'], $webhook['http_method']);
+ }
+ }
+ }
+
+ return $this->voiceConfig;
+ }
+
+ public function getPublicKey()
+ {
+ if(isset($this->keys['public_key'])){
+ return $this->keys['public_key'];
+ }
+ }
+
+ public function getPrivateKey()
+ {
+ if(isset($this->keys['private_key'])){
+ return $this->keys['private_key'];
+ }
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ $this->name = $json['name'];
+ $this->id = $json['id'];
+ $this->keys = $json['keys'];
+
+ //todo: make voice hydrate-able
+ $this->voiceConfig = new VoiceConfig();
+ if(isset($json['voice']) AND isset($json['voice']['webhooks'])){
+ foreach($json['voice']['webhooks'] as $webhook){
+ $this->voiceConfig->setWebhook($webhook['endpoint_type'], new Webhook($webhook['endpoint'], $webhook['http_method']));
+ }
+ }
+ }
+
+ public function jsonSerialize()
+ {
+ return [
+ 'name' => $this->getName(),
+ //currently, the request data does not match the response data
+ 'event_url' => (string) $this->getVoiceConfig()->getWebhook(VoiceConfig::EVENT),
+ 'answer_url' => (string) $this->getVoiceConfig()->getWebhook(VoiceConfig::ANSWER),
+ 'type' => 'voice' //currently the only type
+ ];
+ }
+
+ public function __toString()
+ {
+ return (string) $this->getId();
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Application/ApplicationInterface.php b/vendor/nexmo/client/src/Application/ApplicationInterface.php
new file mode 100644
index 000000000..297160940
--- /dev/null
+++ b/vendor/nexmo/client/src/Application/ApplicationInterface.php
@@ -0,0 +1,16 @@
+jsonUnserialize($data);
+ return $application;
+ }
+
+ public function get($application)
+ {
+ if(!($application instanceof Application)){
+ $application = new Application($application);
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $this->getCollectionPath() . '/' . $application->getId()
+ ,'GET'
+ );
+
+ $application->setRequest($request);
+ $response = $this->client->send($request);
+ $application->setResponse($response);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response, $application);
+ }
+
+ return $application;
+ }
+
+ public function create($application)
+ {
+ return $this->post($application);
+ }
+
+ public function post($application)
+ {
+ if(!($application instanceof Application)){
+ $application = $this->createFromArray($application);
+ }
+
+ $body = $application->getRequestData(false);
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $this->getCollectionPath()
+ ,'POST',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($body));
+ $application->setRequest($request);
+ $response = $this->client->send($request);
+ $application->setResponse($response);
+
+ if($response->getStatusCode() != '201'){
+ throw $this->getException($response, $application);
+ }
+
+ return $application;
+ }
+
+ public function update($application, $id = null)
+ {
+ return $this->put($application, $id);
+ }
+
+ public function put($application, $id = null)
+ {
+ if(!($application instanceof Application)){
+ $application = $this->createFromArray($application);
+ }
+
+ if(is_null($id)){
+ $id = $application->getId();
+ }
+
+ $body = $application->getRequestData(false);
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $this->getCollectionPath() . '/' . $id,
+ 'PUT',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($body));
+ $application->setRequest($request);
+ $response = $this->client->send($request);
+ $application->setResponse($response);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response, $application);
+ }
+
+ return $application;
+ }
+
+ public function delete($application)
+ {
+ if(($application instanceof Application)){
+ $id = $application->getId();
+ } else {
+ $id = $application;
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl(). $this->getCollectionPath() . '/' . $id
+ ,'DELETE'
+ );
+
+ if($application instanceof Application){
+ $application->setRequest($request);
+ }
+
+ $response = $this->client->send($request);
+
+ if($application instanceof Application){
+ $application->setResponse($response);
+ }
+
+ if($response->getStatusCode() != '204'){
+ throw $this->getException($response, $application);
+ }
+
+ return true;
+ }
+
+ protected function getException(ResponseInterface $response, $application = null)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error_title'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error_title'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ //todo use interfaces here
+ if(($application instanceof Application) AND (($e instanceof Exception\Request) OR ($e instanceof Exception\Server))){
+ $e->setEntity($application);
+ }
+
+ return $e;
+ }
+
+ protected function createFromArray($array)
+ {
+ if(!is_array($array)){
+ throw new \RuntimeException('application must implement `' . ApplicationInterface::class . '` or be an array`');
+ }
+
+ foreach(['name',] as $param){
+ if(!isset($array[$param])){
+ throw new \InvalidArgumentException('missing expected key `' . $param . '`');
+ }
+ }
+
+ $application = new Application();
+ $application->setName($array['name']);
+
+ foreach(['event', 'answer'] as $type){
+ if(isset($array[$type . '_url'])){
+ $method = isset($array[$type . '_method']) ? $array[$type . '_method'] : null;
+ $application->getVoiceConfig()->setWebhook($type . '_url', new Webhook($array[$type . '_url'], $method));
+ }
+ }
+
+ return $application;
+ }
+}
diff --git a/vendor/nexmo/client/src/Application/Filter.php b/vendor/nexmo/client/src/Application/Filter.php
new file mode 100644
index 000000000..b3e13958a
--- /dev/null
+++ b/vendor/nexmo/client/src/Application/Filter.php
@@ -0,0 +1,39 @@
+start = $start;
+ $this->end = $end;
+ } else {
+ $this->start = $end;
+ $this->end = $start;
+ }
+ }
+
+ public function getQuery()
+ {
+ return [
+ 'date' => $this->start->format(self::FORMAT) . '-' . $this->end->format(self::FORMAT)
+ ];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Application/VoiceConfig.php b/vendor/nexmo/client/src/Application/VoiceConfig.php
new file mode 100644
index 000000000..3792618ec
--- /dev/null
+++ b/vendor/nexmo/client/src/Application/VoiceConfig.php
@@ -0,0 +1,34 @@
+webhooks[$type] = $url;
+ return $this;
+ }
+
+ public function getWebhook($type)
+ {
+ if(isset($this->webhooks[$type])){
+ return $this->webhooks[$type];
+ }
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Application/Webhook.php b/vendor/nexmo/client/src/Application/Webhook.php
new file mode 100644
index 000000000..89db994ae
--- /dev/null
+++ b/vendor/nexmo/client/src/Application/Webhook.php
@@ -0,0 +1,46 @@
+url = $url;
+ $this->method = $method;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ public function __toString()
+ {
+ return $this->getUrl();
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Call.php b/vendor/nexmo/client/src/Call/Call.php
new file mode 100644
index 000000000..49b24bd16
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Call.php
@@ -0,0 +1,301 @@
+id = $id;
+ }
+
+ public function get()
+ {
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId()
+ ,'GET'
+ );
+
+ $response = $this->getClient()->send($request);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $data = json_decode($response->getBody()->getContents(), true);
+ $this->jsonUnserialize($data);
+
+ return $this;
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error_title'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error_title'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ }
+
+ return $e;
+ }
+
+ public function put($payload)
+ {
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId()
+ ,'PUT',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($payload));
+ $response = $this->client->send($request);
+
+ $responseCode = $response->getStatusCode();
+ if($responseCode != '200' && $responseCode != '204'){
+ throw $this->getException($response);
+ }
+
+ return $this;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setTo($endpoint)
+ {
+ if(!($endpoint instanceof Endpoint)){
+ $endpoint = new Endpoint($endpoint);
+ }
+
+ $this->to = $endpoint;
+ return $this;
+ }
+
+ /**
+ * @return Endpoint
+ */
+ public function getTo()
+ {
+ if($this->lazyLoad()){
+ return new Endpoint($this->data['to']['number'], $this->data['to']['type']);
+ }
+
+ return $this->to;
+ }
+
+ public function setFrom($endpoint)
+ {
+ if(!($endpoint instanceof Endpoint)){
+ $endpoint = new Endpoint($endpoint);
+ }
+
+ $this->from = $endpoint;
+ return $this;
+ }
+
+ /**
+ * @return Endpoint
+ */
+ public function getFrom()
+ {
+ if($this->lazyLoad()){
+ return new Endpoint($this->data['from']['number'], $this->data['from']['type']);
+ }
+
+ return $this->from;
+ }
+
+ public function setWebhook($type, $url = null, $method = null)
+ {
+ if($type instanceof Webhook){
+ $this->webhooks[$type->getType()] = $type;
+ return $this;
+ }
+
+ if(is_null($url)){
+ throw new \InvalidArgumentException('must provide `Nexmo\Call\Webhook` object, or a type and url: missing url' );
+ }
+
+ $this->webhooks[$type] = new Webhook($type, $url, $method);
+ return $this;
+ }
+
+ public function setTimer($type, $length)
+ {
+ $this->data[$type . '_timer'] = $length;
+ }
+
+ public function setTimeout($type, $length)
+ {
+ $this->data[$type . '_timeout'] = $length;
+ }
+
+ public function getStatus()
+ {
+ if($this->lazyLoad()){
+ return $this->data['status'];
+ }
+ }
+
+ public function getDirection()
+ {
+ if($this->lazyLoad()){
+ return $this->data['direction'];
+ }
+ }
+
+ public function getConversation()
+ {
+ if($this->lazyLoad()){
+ return new Conversation($this->data['conversation_uuid']);
+ }
+ }
+
+ /**
+ * Returns true if the resource data is loaded.
+ *
+ * Will attempt to load the data if it's not already.
+ *
+ * @return bool
+ */
+ protected function lazyLoad()
+ {
+ if(!empty($this->data)){
+ return true;
+ }
+
+ if(isset($this->id)){
+ $this->get($this);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function __get($name)
+ {
+ switch($name){
+ case 'stream':
+ case 'talk':
+ case 'dtmf':
+ return $this->lazySubresource(ucfirst($name));
+ default:
+ throw new \RuntimeException('property does not exist: ' . $name);
+ }
+ }
+
+ public function __call($name, $arguments)
+ {
+ switch($name){
+ case 'stream':
+ case 'talk':
+ case 'dtmf':
+ $entity = $this->lazySubresource(ucfirst($name));
+ return call_user_func_array($entity, $arguments);
+ default:
+ throw new \RuntimeException('method does not exist: ' . $name);
+ }
+ }
+
+ protected function lazySubresource($type)
+ {
+ if(!isset($this->subresources[$type])){
+ $class = 'Nexmo\Call\\' . $type;
+ $instance = new $class($this->getId());
+ $instance->setClient($this->getClient());
+ $this->subresources[$type] = $instance;
+ }
+
+ return $this->subresources[$type];
+ }
+
+ public function jsonSerialize()
+ {
+ $data = $this->data;
+
+ if(isset($this->to)){
+ $data['to'] = [$this->to->jsonSerialize()];
+ }
+
+ if(isset($this->from)){
+ $data['from'] = $this->from->jsonSerialize();
+ }
+
+ foreach($this->webhooks as $webhook){
+ $data = array_merge($data, $webhook->jsonSerialize());
+ }
+
+ return $data;
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ $this->data = $json;
+ $this->id = $json['uuid'];
+ }
+}
diff --git a/vendor/nexmo/client/src/Call/Collection.php b/vendor/nexmo/client/src/Call/Collection.php
new file mode 100644
index 000000000..0a710f0d6
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Collection.php
@@ -0,0 +1,203 @@
+setClient($this->getClient());
+ $idOrCall->jsonUnserialize($data);
+
+ return $idOrCall;
+ }
+
+ /**
+ * @param null $callOrFilter
+ * @return $this|Call
+ */
+ public function __invoke(Filter $filter = null)
+ {
+ if(!is_null($filter)){
+ $this->setFilter($filter);
+ }
+
+ return $this;
+ }
+
+ public function create($call)
+ {
+ return $this->post($call);
+ }
+
+ public function put($payload, $idOrCall)
+ {
+ if(!($idOrCall instanceof Call)){
+ $idOrCall = new Call($idOrCall);
+ }
+
+ $idOrCall->setClient($this->getClient());
+ $idOrCall->put($payload);
+ return $idOrCall;
+ }
+
+ public function delete($call = null, $type)
+ {
+ if(is_object($call) AND is_callable([$call, 'getId'])){
+ $call = $call->getId();
+ }
+
+ if(!($call instanceof Call)){
+ $call = new Call($call);
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $this->getCollectionPath() . '/' . $call->getId() . '/' . $type
+ ,'DELETE'
+ );
+
+ $response = $this->client->send($request);
+
+ if($response->getStatusCode() != '204'){
+ throw $this->getException($response);
+ }
+
+ return $call;
+ }
+
+ public function post($call)
+ {
+ if($call instanceof Call){
+ $body = $call->getRequestData();
+ } else {
+ $body = $call;
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $this->getCollectionPath()
+ ,'POST',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($body));
+ $response = $this->client->send($request);
+
+ if($response->getStatusCode() != '201'){
+ throw $this->getException($response);
+ }
+
+ $body = json_decode($response->getBody()->getContents(), true);
+ $call = new Call($body['uuid']);
+ $call->jsonUnserialize($body);
+ $call->setClient($this->getClient());
+
+ return $call;
+ }
+
+ public function get($call)
+ {
+ if(!($call instanceof Call)){
+ $call = new Call($call);
+ }
+
+ $call->setClient($this->getClient());
+ $call->get();
+
+ return $call;
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ // Error responses aren't consistent. Some are generated within the
+ // proxy and some are generated within voice itself. This handles
+ // both cases
+
+ // This message isn't very useful, but we shouldn't ever see it
+ $errorTitle = 'Unexpected error';
+
+ if (isset($body['title'])) {
+ $errorTitle = $body['title'];
+ }
+
+ if (isset($body['error_title'])) {
+ $errorTitle = $body['error_title'];
+ }
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($errorTitle, $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($errorTitle, $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+ public function offsetExists($offset)
+ {
+ //todo: validate form of id
+ return true;
+ }
+
+ /**
+ * @param mixed $call
+ * @return Call
+ */
+ public function offsetGet($call)
+ {
+ if(!($call instanceof Call)){
+ $call = new Call($call);
+ }
+
+ $call->setClient($this->getClient());
+ return $call;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new \RuntimeException('can not set collection properties');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new \RuntimeException('can not unset collection properties');
+ }
+}
diff --git a/vendor/nexmo/client/src/Call/Dtmf.php b/vendor/nexmo/client/src/Call/Dtmf.php
new file mode 100644
index 000000000..691c2f389
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Dtmf.php
@@ -0,0 +1,139 @@
+id = $id;
+ }
+
+ public function __invoke(self $entity = null)
+ {
+ if(is_null($entity)){
+ return $this;
+ }
+
+ return $this->put($entity);
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setDigits($digits)
+ {
+ $this->data['digits'] = (string) $digits;
+ }
+
+ public function put($dtmf = null)
+ {
+ if(!$dtmf){
+ $dtmf = $this;
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId() . '/dtmf',
+ 'PUT',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($dtmf));
+ $response = $this->client->send($request);
+ return $this->parseEventResponse($response);
+ }
+
+ protected function parseEventResponse(ResponseInterface $response)
+ {
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $json = json_decode($response->getBody()->getContents(), true);
+
+ if(!$json){
+ throw new Exception\Exception('Unexpected Response Body Format');
+ }
+
+ return new Event($json);
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error_title'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error_title'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+ function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if(!in_array($offset, $this->params)){
+ throw new \RuntimeException('invalid parameter: ' . $offset);
+ }
+
+ $this->data[$offset] = $value;
+ }
+
+ public function offsetUnset($offset)
+ {
+ if(!in_array($offset, $this->params)){
+ throw new \RuntimeException('invalid parameter: ' . $offset);
+ }
+
+ unset($this->data[$offset]);
+ }
+}
diff --git a/vendor/nexmo/client/src/Call/Earmuff.php b/vendor/nexmo/client/src/Call/Earmuff.php
new file mode 100644
index 000000000..32cc0e115
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Earmuff.php
@@ -0,0 +1,20 @@
+ 'earmuff'
+ ];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Endpoint.php b/vendor/nexmo/client/src/Call/Endpoint.php
new file mode 100644
index 000000000..a25db5215
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Endpoint.php
@@ -0,0 +1,87 @@
+id = $id;
+ $this->type = $type;
+ $this->additional = $additional;
+ }
+
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function set($property, $value)
+ {
+ $this->additional[$property] = $value;
+ return $this;
+ }
+
+ public function get($property)
+ {
+ if(isset($this->additional[$property])){
+ return $this->additional[$property];
+ }
+ }
+
+ public function getNumber()
+ {
+ if(!self::PHONE == $this->type){
+ throw new \RuntimeException('number not defined for this type');
+ }
+
+ return $this->getId();
+ }
+
+ public function __toString()
+ {
+ return (string) $this->getId();
+ }
+
+ function jsonSerialize()
+ {
+ switch($this->type){
+ case 'phone':
+ return array_merge(
+ $this->additional,
+ [
+ 'type' => $this->type,
+ 'number' => $this->id
+ ]
+
+ );
+ default:
+ throw new \RuntimeException('unknown type: ' . $this->type);
+ }
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Event.php b/vendor/nexmo/client/src/Call/Event.php
new file mode 100644
index 000000000..b141be16d
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Event.php
@@ -0,0 +1,53 @@
+data = $data;
+ }
+
+ public function getId()
+ {
+ return $this->data['uuid'];
+ }
+
+ public function getMessage()
+ {
+ return $this->data['message'];
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new \RuntimeException('can not set properties directly');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new \RuntimeException('can not set properties directly');
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Filter.php b/vendor/nexmo/client/src/Call/Filter.php
new file mode 100644
index 000000000..efbf0d925
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Filter.php
@@ -0,0 +1,81 @@
+query;
+ }
+
+ public function sortAscending()
+ {
+ return $this->setOrder('asc');
+ }
+
+ public function sortDescending()
+ {
+ return $this->setOrder('desc');
+ }
+
+ public function setStatus($status)
+ {
+ $this->query['status'] = (string) $status;
+ return $this;
+ }
+
+ public function setStart(\DateTime $start)
+ {
+ $start->setTimezone(new \DateTimeZone("UTC"));
+ $this->query['date_start'] = $start->format('Y-m-d\TH:i:s\Z');
+ return $this;
+ }
+
+ public function setEnd(\DateTime $end)
+ {
+ $end->setTimezone(new \DateTimeZone("UTC"));
+ $this->query['date_end'] = $end->format('Y-m-d\TH:i:s\Z');
+ return $this;
+ }
+
+ public function setSize($size)
+ {
+ $this->query['page_size'] = (int) $size;
+ return $this;
+ }
+
+ public function setIndex($index)
+ {
+ $this->query['record_index'] = (int) $index;
+ return $this;
+ }
+
+ public function setOrder($order)
+ {
+ $this->query['order'] = (string) $order;
+ return $this;
+ }
+
+ public function setConversation($conversation)
+ {
+ if($conversation instanceof Conversation){
+ $conversation = $conversation->getId();
+ }
+
+ $this->query['conversation_uuid'] = $conversation;
+
+ return $this;
+ }
+}
diff --git a/vendor/nexmo/client/src/Call/Hangup.php b/vendor/nexmo/client/src/Call/Hangup.php
new file mode 100644
index 000000000..48afd81ef
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Hangup.php
@@ -0,0 +1,21 @@
+ 'hangup'
+ ];
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Mute.php b/vendor/nexmo/client/src/Call/Mute.php
new file mode 100644
index 000000000..2702bc88d
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Mute.php
@@ -0,0 +1,20 @@
+ 'mute'
+ ];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Stream.php b/vendor/nexmo/client/src/Call/Stream.php
new file mode 100644
index 000000000..d0c16de9c
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Stream.php
@@ -0,0 +1,127 @@
+id = $id;
+ }
+
+ public function __invoke(Stream $stream = null)
+ {
+ if(is_null($stream)){
+ return $this;
+ }
+
+ return $this->put($stream);
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setUrl($url)
+ {
+ if(!is_array($url)){
+ $url = array($url);
+ }
+
+ $this->data['stream_url'] = $url;
+ }
+
+ public function setLoop($times)
+ {
+ $this->data['loop'] = (int) $times;
+ }
+
+ public function put($stream = null)
+ {
+ if(!$stream){
+ $stream = $this;
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId() . '/stream',
+ 'PUT',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($stream));
+ $response = $this->client->send($request);
+ return $this->parseEventResponse($response);
+ }
+
+ public function delete()
+ {
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId() . '/stream',
+ 'DELETE'
+ );
+
+ $response = $this->client->send($request);
+ return $this->parseEventResponse($response);
+ }
+
+ protected function parseEventResponse(ResponseInterface $response)
+ {
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $json = json_decode($response->getBody()->getContents(), true);
+
+ if(!$json){
+ throw new Exception\Exception('Unexpected Response Body Format');
+ }
+
+ return new Event($json);
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error_title'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error_title'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+ function jsonSerialize()
+ {
+ return $this->data;
+ }
+}
diff --git a/vendor/nexmo/client/src/Call/Talk.php b/vendor/nexmo/client/src/Call/Talk.php
new file mode 100644
index 000000000..a16acefd1
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Talk.php
@@ -0,0 +1,162 @@
+id = $id;
+ }
+
+ public function __invoke(self $entity = null)
+ {
+ if(is_null($entity)){
+ return $this;
+ }
+
+ return $this->put($entity);
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setText($text)
+ {
+ $this->data['text'] = (string) $text;
+ }
+
+ public function setVoiceName($name)
+ {
+ $this->data['voice_name'] = (string) $name;
+ }
+
+ public function setLoop($times)
+ {
+ $this->data['loop'] = (int) $times;
+ }
+
+ public function put($talk = null)
+ {
+ if(!$talk){
+ $talk = $this;
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId() . '/talk',
+ 'PUT',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($talk));
+ $response = $this->client->send($request);
+ return $this->parseEventResponse($response);
+ }
+
+ public function delete()
+ {
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId() . '/talk',
+ 'DELETE'
+ );
+
+ $response = $this->client->send($request);
+ return $this->parseEventResponse($response);
+ }
+
+ protected function parseEventResponse(ResponseInterface $response)
+ {
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $json = json_decode($response->getBody()->getContents(), true);
+
+ if(!$json){
+ throw new Exception\Exception('Unexpected Response Body Format');
+ }
+
+ return new Event($json);
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error_title'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error_title'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+ function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if(!in_array($offset, $this->params)){
+ throw new \RuntimeException('invalid parameter: ' . $offset);
+ }
+
+ $this->data[$offset] = $value;
+ }
+
+ public function offsetUnset($offset)
+ {
+ if(!in_array($offset, $this->params)){
+ throw new \RuntimeException('invalid parameter: ' . $offset);
+ }
+
+ unset($this->data[$offset]);
+ }
+}
diff --git a/vendor/nexmo/client/src/Call/Transfer.php b/vendor/nexmo/client/src/Call/Transfer.php
new file mode 100644
index 000000000..6c748973e
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Transfer.php
@@ -0,0 +1,35 @@
+urls = $urls;
+ }
+
+ function jsonSerialize()
+ {
+ return [
+ 'action' => 'transfer',
+ 'destination' => [
+ 'type' => 'ncco',
+ 'url' => $this->urls
+ ]
+ ];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Unearmuff.php b/vendor/nexmo/client/src/Call/Unearmuff.php
new file mode 100644
index 000000000..5b13e3d57
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Unearmuff.php
@@ -0,0 +1,20 @@
+ 'unearmuff'
+ ];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Unmute.php b/vendor/nexmo/client/src/Call/Unmute.php
new file mode 100644
index 000000000..05c0b2208
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Unmute.php
@@ -0,0 +1,20 @@
+ 'unmute'
+ ];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Call/Webhook.php b/vendor/nexmo/client/src/Call/Webhook.php
new file mode 100644
index 000000000..ebb1c2d2f
--- /dev/null
+++ b/vendor/nexmo/client/src/Call/Webhook.php
@@ -0,0 +1,55 @@
+urls = $urls;
+ $this->type = $type;
+ $this->method = $method;
+ }
+
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function add($url)
+ {
+ $this->urls[] = $url;
+ }
+
+ function jsonSerialize()
+ {
+ $data = [
+ $this->type . '_url' => $this->urls
+ ];
+
+ if(isset($this->method)){
+ $data[$this->type . '_method'] = $this->method;
+ }
+
+ return $data;
+ }
+
+
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client.php b/vendor/nexmo/client/src/Client.php
new file mode 100644
index 000000000..899f6c86e
--- /dev/null
+++ b/vendor/nexmo/client/src/Client.php
@@ -0,0 +1,510 @@
+setHttpClient($client);
+
+ //make sure we know how to use the credentials
+ if(!($credentials instanceof Container) && !($credentials instanceof Basic) && !($credentials instanceof SignatureSecret) && !($credentials instanceof OAuth) && !($credentials instanceof Keypair)){
+ throw new \RuntimeException('unknown credentials type: ' . get_class($credentials));
+ }
+
+ $this->credentials = $credentials;
+
+ $this->options = $options;
+
+ // If they've provided an app name, validate it
+ if (isset($options['app'])) {
+ $this->validateAppOptions($options['app']);
+ }
+
+ // Set the default URLs. Keep the constants for
+ // backwards compatibility
+ $this->apiUrl = static::BASE_API;
+ $this->restUrl = static::BASE_REST;
+
+ // If they've provided alternative URLs, use that instead
+ // of the defaults
+ if (isset($options['base_rest_url'])) {
+ $this->restUrl = $options['base_rest_url'];
+ }
+
+ if (isset($options['base_api_url'])) {
+ $this->apiUrl = $options['base_api_url'];
+ }
+
+ $this->setFactory(new MapFactory([
+ 'account' => 'Nexmo\Account\Client',
+ 'insights' => 'Nexmo\Insights\Client',
+ 'message' => 'Nexmo\Message\Client',
+ 'verify' => 'Nexmo\Verify\Client',
+ 'applications' => 'Nexmo\Application\Client',
+ 'numbers' => 'Nexmo\Numbers\Client',
+ 'calls' => 'Nexmo\Call\Collection',
+ 'conversion' => 'Nexmo\Conversion\Client',
+ 'conversation' => 'Nexmo\Conversations\Collection',
+ 'user' => 'Nexmo\User\Collection',
+ 'redact' => 'Nexmo\Redact\Client',
+ ], $this));
+ }
+
+ public function getRestUrl() {
+ return $this->restUrl;
+ }
+
+ public function getApiUrl() {
+ return $this->apiUrl;
+ }
+
+ /**
+ * Set the Http Client to used to make API requests.
+ *
+ * This allows the default http client to be swapped out for a HTTPlug compatible
+ * replacement.
+ *
+ * @param HttpClient $client
+ * @return $this
+ */
+ public function setHttpClient(HttpClient $client)
+ {
+ $this->client = $client;
+ return $this;
+ }
+
+ /**
+ * Get the Http Client used to make API requests.
+ *
+ * @return HttpClient
+ */
+ public function getHttpClient()
+ {
+ return $this->client;
+ }
+
+ /**
+ * Set the factory used to create API specific clients.
+ *
+ * @param FactoryInterface $factory
+ * @return $this
+ */
+ public function setFactory(FactoryInterface $factory)
+ {
+ $this->factory = $factory;
+ return $this;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param Signature $signature
+ * @return RequestInterface
+ */
+ public static function signRequest(RequestInterface $request, SignatureSecret $credentials)
+ {
+ switch($request->getHeaderLine('content-type')){
+ case 'application/json':
+ $body = $request->getBody();
+ $body->rewind();
+ $content = $body->getContents();
+ $params = json_decode($content, true);
+ $params['api_key'] = $credentials['api_key'];
+ $signature = new Signature($params, $credentials['signature_secret'], $credentials['signature_method']);
+ $body->rewind();
+ $body->write(json_encode($signature->getSignedParams()));
+ break;
+ case 'application/x-www-form-urlencoded':
+ $body = $request->getBody();
+ $body->rewind();
+ $content = $body->getContents();
+ $params = [];
+ parse_str($content, $params);
+ $params['api_key'] = $credentials['api_key'];
+ $signature = new Signature($params, $credentials['signature_secret'], $credentials['signature_method']);
+ $params = $signature->getSignedParams();
+ $body->rewind();
+ $body->write(http_build_query($params, null, '&'));
+ break;
+ default:
+ $query = [];
+ parse_str($request->getUri()->getQuery(), $query);
+ $query['api_key'] = $credentials['api_key'];
+ $signature = new Signature($query, $credentials['signature_secret'], $credentials['signature_method']);
+ $request = $request->withUri($request->getUri()->withQuery(http_build_query($signature->getSignedParams())));
+ break;
+ }
+
+ return $request;
+ }
+
+ public static function authRequest(RequestInterface $request, Basic $credentials)
+ {
+ switch($request->getHeaderLine('content-type')) {
+ case 'application/json':
+ if (static::requiresBasicAuth($request)) {
+ $c = $credentials->asArray();
+ $request = $request->withHeader('Authorization', 'Basic ' . base64_encode($c['api_key'] . ':' . $c['api_secret']));
+ } else if (static::requiresAuthInUrlNotBody($request)) {
+ $query = [];
+ parse_str($request->getUri()->getQuery(), $query);
+ $query = array_merge($query, $credentials->asArray());
+ $request = $request->withUri($request->getUri()->withQuery(http_build_query($query)));
+ } else {
+ $body = $request->getBody();
+ $body->rewind();
+ $content = $body->getContents();
+ $params = json_decode($content, true);
+ $params = array_merge($params, $credentials->asArray());
+ $body->rewind();
+ $body->write(json_encode($params));
+ }
+ break;
+ case 'application/x-www-form-urlencoded':
+ $body = $request->getBody();
+ $body->rewind();
+ $content = $body->getContents();
+ $params = [];
+ parse_str($content, $params);
+ $params = array_merge($params, $credentials->asArray());
+ $body->rewind();
+ $body->write(http_build_query($params, null, '&'));
+ break;
+ default:
+ $query = [];
+ parse_str($request->getUri()->getQuery(), $query);
+ $query = array_merge($query, $credentials->asArray());
+ $request = $request->withUri($request->getUri()->withQuery(http_build_query($query)));
+ break;
+ }
+
+ return $request;
+ }
+
+ /**
+ * @param array $claims
+ * @return \Lcobucci\JWT\Token
+ */
+ public function generateJwt($claims = [])
+ {
+ if (method_exists($this->credentials, "generateJwt")) {
+ return $this->credentials->generateJwt($claims);
+ }
+ throw new Exception(get_class($this->credentials).' does not support JWT generation');
+ }
+
+ /**
+ * Takes a URL and a key=>value array to generate a GET PSR-7 request object
+ *
+ * @param string $url The URL to make a request to
+ * @param array $params Key=>Value array of data to use as the query string
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function get($url, array $params = [])
+ {
+ $queryString = '?' . http_build_query($params);
+
+ $url = $url . $queryString;
+
+ $request = new Request(
+ $url,
+ 'GET'
+ );
+
+ return $this->send($request);
+ }
+
+ /**
+ * Takes a URL and a key=>value array to generate a POST PSR-7 request object
+ *
+ * @param string $url The URL to make a request to
+ * @param array $params Key=>Value array of data to send
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function post($url, array $params)
+ {
+ $request = new Request(
+ $url,
+ 'POST',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($params));
+ return $this->send($request);
+ }
+
+ /**
+ * Takes a URL and a key=>value array to generate a POST PSR-7 request object
+ *
+ * @param string $url The URL to make a request to
+ * @param array $params Key=>Value array of data to send
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function postUrlEncoded($url, array $params)
+ {
+ $request = new Request(
+ $url,
+ 'POST',
+ 'php://temp',
+ ['content-type' => 'application/x-www-form-urlencoded']
+ );
+
+ $request->getBody()->write(http_build_query($params));
+ return $this->send($request);
+ }
+
+ /**
+ * Takes a URL and a key=>value array to generate a PUT PSR-7 request object
+ *
+ * @param string $url The URL to make a request to
+ * @param array $params Key=>Value array of data to send
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function put($url, array $params)
+ {
+ $request = new Request(
+ $url,
+ 'PUT',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($params));
+ return $this->send($request);
+ }
+
+ /**
+ * Takes a URL and a key=>value array to generate a DELETE PSR-7 request object
+ *
+ * @param string $url The URL to make a request to
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function delete($url)
+ {
+ $request = new Request(
+ $url,
+ 'DELETE'
+ );
+
+ return $this->send($request);
+ }
+
+ /**
+ * Wraps the HTTP Client, creates a new PSR-7 request adding authentication, signatures, etc.
+ *
+ * @param \Psr\Http\Message\RequestInterface $request
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function send(\Psr\Http\Message\RequestInterface $request)
+ {
+ if($this->credentials instanceof Container) {
+ if ($this->needsKeypairAuthentication($request)) {
+ $request = $request->withHeader('Authorization', 'Bearer ' . $this->credentials->get(Keypair::class)->generateJwt());
+ } else {
+ $request = self::authRequest($request, $this->credentials->get(Basic::class));
+ }
+ } elseif($this->credentials instanceof Keypair){
+ $request = $request->withHeader('Authorization', 'Bearer ' . $this->credentials->generateJwt());
+ } elseif($this->credentials instanceof SignatureSecret){
+ $request = self::signRequest($request, $this->credentials);
+ } elseif($this->credentials instanceof Basic){
+ $request = self::authRequest($request, $this->credentials);
+ }
+
+ //todo: add oauth support
+
+ //allow any part of the URI to be replaced with a simple search
+ if(isset($this->options['url'])){
+ foreach($this->options['url'] as $search => $replace){
+ $uri = (string) $request->getUri();
+
+ $new = str_replace($search, $replace, $uri);
+ if($uri !== $new){
+ $request = $request->withUri(new Uri($new));
+ }
+ }
+ }
+
+ // The user agent must be in the following format:
+ // LIBRARY-NAME/LIBRARY-VERSION LANGUAGE-NAME/LANGUAGE-VERSION [APP-NAME/APP-VERSION]
+ // See https://github.com/Nexmo/client-library-specification/blob/master/SPECIFICATION.md#reporting
+ $userAgent = [];
+
+ // Library name
+ $userAgent[] = 'nexmo-php/'.self::VERSION;
+
+ // Language name
+ $userAgent[] = 'php/'.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;
+
+ // If we have an app set, add that to the UA
+ if (isset($this->options['app'])) {
+ $app = $this->options['app'];
+ $userAgent[] = $app['name'].'/'.$app['version'];
+ }
+
+ // Set the header. Build by joining all the parts we have with a space
+ $request = $request->withHeader('User-Agent', implode(" ", $userAgent));
+
+ $response = $this->client->sendRequest($request);
+ return $response;
+ }
+
+ protected function validateAppOptions($app) {
+ $disallowedCharacters = ['/', ' ', "\t", "\n"];
+ foreach (['name', 'version'] as $key) {
+ if (!isset($app[$key])) {
+ throw new \InvalidArgumentException('app.'.$key.' has not been set');
+ }
+
+ foreach ($disallowedCharacters as $char) {
+ if (strpos($app[$key], $char) !== false) {
+ throw new \InvalidArgumentException('app.'.$key.' cannot contain the '.$char.' character');
+ }
+ }
+ }
+ }
+
+ public function serialize(EntityInterface $entity)
+ {
+ if($entity instanceof Verification){
+ return $this->verify()->serialize($entity);
+ }
+
+ throw new \RuntimeException('unknown class `' . get_class($entity) . '``');
+ }
+
+ public function unserialize($entity)
+ {
+ if(is_string($entity)){
+ $entity = unserialize($entity);
+ }
+
+ if($entity instanceof Verification){
+ return $this->verify()->unserialize($entity);
+ }
+
+ throw new \RuntimeException('unknown class `' . get_class($entity) . '``');
+ }
+
+ public function __call($name, $args)
+ {
+ if(!$this->factory->hasApi($name)){
+ throw new \RuntimeException('no api namespace found: ' . $name);
+ }
+
+ $collection = $this->factory->getApi($name);
+
+ if(empty($args)){
+ return $collection;
+ }
+
+ return call_user_func_array($collection, $args);
+ }
+
+ public function __get($name)
+ {
+ if(!$this->factory->hasApi($name)){
+ throw new \RuntimeException('no api namespace found: ' . $name);
+ }
+
+ return $this->factory->getApi($name);
+ }
+
+ protected static function requiresBasicAuth(\Psr\Http\Message\RequestInterface $request)
+ {
+ $path = $request->getUri()->getPath();
+ $isSecretManagementEndpoint = strpos($path, '/accounts') === 0 && strpos($path, '/secrets') !== false;
+
+ return $isSecretManagementEndpoint;
+ }
+
+ protected static function requiresAuthInUrlNotBody(\Psr\Http\Message\RequestInterface $request)
+ {
+ $path = $request->getUri()->getPath();
+ $isRedactEndpoint = strpos($path, '/v1/redact') === 0;
+
+ return $isRedactEndpoint;
+ }
+
+ protected function needsKeypairAuthentication(\Psr\Http\Message\RequestInterface $request)
+ {
+ $path = $request->getUri()->getPath();
+ $isCallEndpoint = strpos($path, '/v1/calls') === 0;
+ $isRecordingUrl = strpos($path, '/v1/files') === 0;
+ $isStitchEndpoint = strpos($path, '/beta/conversation') === 0;
+ $isUserEndpoint = strpos($path, '/beta/users') === 0;
+
+ return $isCallEndpoint || $isRecordingUrl || $isStitchEndpoint || $isUserEndpoint;
+ }
+}
diff --git a/vendor/nexmo/client/src/Client/Callback/Callback.php b/vendor/nexmo/client/src/Client/Callback/Callback.php
new file mode 100644
index 000000000..d1b2175e5
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Callback/Callback.php
@@ -0,0 +1,57 @@
+expected, $keys);
+
+ if($missing){
+ throw new \RuntimeException('missing expected callback keys: ' . implode(', ', $missing));
+ }
+
+ $this->data = $data;
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public static function fromEnv($source = self::ENV_ALL)
+ {
+ switch(strtolower($source)){
+ case 'post':
+ $data = $_POST;
+ break;
+ case 'get':
+ $data = $_GET;
+ break;
+ case 'all':
+ $data = array_merge($_GET, $_POST);
+ break;
+ default:
+ throw new \InvalidArgumentException('invalid source: ' . $source);
+ }
+
+ return new static($data);
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Callback/CallbackInterface.php b/vendor/nexmo/client/src/Client/Callback/CallbackInterface.php
new file mode 100644
index 000000000..3ed342cdf
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Callback/CallbackInterface.php
@@ -0,0 +1,14 @@
+client = $client;
+ }
+
+ protected function getClient()
+ {
+ if(isset($this->client)){
+ return $this->client;
+ }
+
+ throw new \RuntimeException('Nexmo\Client not set');
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Credentials/AbstractCredentials.php b/vendor/nexmo/client/src/Client/Credentials/AbstractCredentials.php
new file mode 100644
index 000000000..c4ae08ce9
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Credentials/AbstractCredentials.php
@@ -0,0 +1,53 @@
+credentials[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->credentials[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw $this->readOnlyException();
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw $this->readOnlyException();
+ }
+
+ public function __get($name)
+ {
+ return $this->credentials[$name];
+ }
+
+ public function asArray()
+ {
+ return $this->credentials;
+ }
+
+ protected function readOnlyException()
+ {
+ return new \RuntimeException(sprintf(
+ '%s is read only, cannot modify using array access.',
+ get_class($this)
+ ));
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Credentials/Basic.php b/vendor/nexmo/client/src/Client/Credentials/Basic.php
new file mode 100644
index 000000000..f5991c4c1
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Credentials/Basic.php
@@ -0,0 +1,28 @@
+credentials['api_key'] = $key;
+ $this->credentials['api_secret'] = $secret;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Credentials/Container.php b/vendor/nexmo/client/src/Client/Credentials/Container.php
new file mode 100644
index 000000000..62d739b73
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Credentials/Container.php
@@ -0,0 +1,69 @@
+addCredential($credential);
+ }
+ }
+
+ protected function addCredential(CredentialsInterface $credential)
+ {
+ $type = $this->getType($credential);
+ if(isset($this->credentials[$type])){
+ throw new \RuntimeException('can not use more than one of a single credential type');
+ }
+
+ $this->credentials[$type] = $credential;
+ }
+
+ protected function getType(CredentialsInterface $credential)
+ {
+ foreach ($this->types as $type) {
+ if($credential instanceof $type){
+ return $type;
+ }
+ }
+ }
+
+ public function get($type)
+ {
+ if(!isset($this->credentials[$type])){
+ throw new \RuntimeException('credental not set');
+ }
+
+ return $this->credentials[$type];
+ }
+
+ public function has($type)
+ {
+ return isset($this->credentials[$type]);
+ }
+
+ public function generateJwt($claims) {
+ return $this->credentials[Keypair::class]->generateJwt($claims);
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Credentials/CredentialsInterface.php b/vendor/nexmo/client/src/Client/Credentials/CredentialsInterface.php
new file mode 100644
index 000000000..7b92501a8
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Credentials/CredentialsInterface.php
@@ -0,0 +1,14 @@
+credentials['key'] = $privateKey;
+ if($application){
+ if($application instanceof Application){
+ $application = $application->getId();
+ }
+
+ $this->credentials['application'] = $application;
+ }
+
+ $this->key = new Key($privateKey);
+ $this->signer = new Sha256();
+ }
+
+ public function generateJwt(array $claims = [])
+ {
+ $exp = time() + 60;
+ $iat = time();
+ $jti = base64_encode(mt_rand());
+
+ if(isset($claims['exp'])){
+ $exp = $claims['exp'];
+ unset($claims['exp']);
+ }
+
+ if(isset($claims['iat'])){
+ $iat = $claims['iat'];
+ unset($claims['iat']);
+ }
+
+ if(isset($claims['jti'])){
+ $jti = $claims['jti'];
+ unset($claims['jti']);
+ }
+
+ $builder = new Builder();
+ $builder->setIssuedAt($iat)
+ ->setExpiration($exp)
+ ->setId($jti);
+
+
+ if(isset($claims['nbf'])){
+ $builder->setNotBefore($claims['nbf']);
+ unset($claims['nbf']);
+ }
+
+ if(isset($this->credentials['application'])){
+ $builder->set('application_id', $this->credentials['application']);
+ }
+
+ if(!empty($claims)){
+ foreach($claims as $claim => $value){
+ $builder->set($claim, $value);
+ }
+ }
+
+ return $builder->sign($this->signer, $this->key)->getToken();
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Credentials/OAuth.php b/vendor/nexmo/client/src/Client/Credentials/OAuth.php
new file mode 100644
index 000000000..b5c83924f
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Credentials/OAuth.php
@@ -0,0 +1,26 @@
+credentials = array_combine(array('consumer_key', 'consumer_secret', 'token', 'token_secret'), func_get_args());
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Credentials/SignatureSecret.php b/vendor/nexmo/client/src/Client/Credentials/SignatureSecret.php
new file mode 100644
index 000000000..f9f232771
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Credentials/SignatureSecret.php
@@ -0,0 +1,25 @@
+credentials['api_key'] = $key;
+ $this->credentials['signature_secret'] = $signature_secret;
+ $this->credentials['signature_method'] = $method;
+ }
+}
diff --git a/vendor/nexmo/client/src/Client/Exception/Exception.php b/vendor/nexmo/client/src/Client/Exception/Exception.php
new file mode 100644
index 000000000..1089f2961
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Exception/Exception.php
@@ -0,0 +1,14 @@
+errors = $errors;
+ parent::__construct($message, $code, $previous);
+ }
+
+ public function getValidationErrors() {
+ return $this->errors;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Factory/FactoryInterface.php b/vendor/nexmo/client/src/Client/Factory/FactoryInterface.php
new file mode 100644
index 000000000..55e27e294
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Factory/FactoryInterface.php
@@ -0,0 +1,30 @@
+map = $map;
+ $this->client = $client;
+ }
+
+ public function hasApi($api)
+ {
+ return isset($this->map[$api]);
+ }
+
+ public function getApi($api)
+ {
+ if(isset($this->cache[$api])){
+ return $this->cache[$api];
+ }
+
+ if(!$this->hasApi($api)){
+ throw new \RuntimeException(sprintf(
+ 'no map defined for `%s`',
+ $api
+ ));
+ }
+
+ $class = $this->map[$api];
+
+ $instance = new $class();
+ if($instance instanceof Client\ClientAwareInterface){
+ $instance->setClient($this->client);
+ }
+ $this->cache[$api] = $instance;
+ return $instance;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Request/AbstractRequest.php b/vendor/nexmo/client/src/Client/Request/AbstractRequest.php
new file mode 100644
index 000000000..f1b9f15aa
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Request/AbstractRequest.php
@@ -0,0 +1,22 @@
+params, 'is_scalar');
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Request/RequestInterface.php b/vendor/nexmo/client/src/Client/Request/RequestInterface.php
new file mode 100644
index 000000000..2aa435bc0
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Request/RequestInterface.php
@@ -0,0 +1,21 @@
+data;
+ }
+
+ public function isSuccess()
+ {
+ return isset($this->data['status']) AND $this->data['status'] == 0;
+ }
+
+ public function isError()
+ {
+ return !$this->isSuccess();
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Response/Error.php b/vendor/nexmo/client/src/Client/Response/Error.php
new file mode 100644
index 000000000..9a384048e
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Response/Error.php
@@ -0,0 +1,45 @@
+expected = ['status', 'error-text'];
+
+ return parent::__construct($data);
+ }
+
+ public function isError()
+ {
+ return true;
+ }
+
+ public function isSuccess()
+ {
+ return false;
+ }
+
+ public function getCode()
+ {
+ return $this->data['status'];
+ }
+
+ public function getMessage()
+ {
+ return $this->data['error-text'];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Response/Response.php b/vendor/nexmo/client/src/Client/Response/Response.php
new file mode 100644
index 000000000..2207c4c96
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Response/Response.php
@@ -0,0 +1,30 @@
+expected, $keys);
+
+ if($missing){
+ throw new \RuntimeException('missing expected response keys: ' . implode(', ', $missing));
+ }
+
+ $this->data = $data;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Client/Response/ResponseInterface.php b/vendor/nexmo/client/src/Client/Response/ResponseInterface.php
new file mode 100644
index 000000000..fa18f5ceb
--- /dev/null
+++ b/vendor/nexmo/client/src/Client/Response/ResponseInterface.php
@@ -0,0 +1,17 @@
+params = $params;
+ $this->signed = $params;
+
+ if(!isset($this->signed['timestamp'])){
+ $this->signed['timestamp'] = time();
+ }
+
+ //remove signature if present
+ unset($this->signed['sig']);
+
+ //sort params
+ ksort($this->signed);
+
+ $signed = [];
+ foreach ($this->signed as $key => $value) {
+ $signed[$key] = str_replace(array("&", "="), "_", $value);
+ }
+
+ //create base string
+ $base = '&'.urldecode(http_build_query($signed));
+
+ $this->signed['sig'] = $this->sign($signatureMethod, $base, $secret);
+ }
+
+ protected function sign($signatureMethod, $data, $secret) {
+ switch($signatureMethod) {
+ case 'md5hash':
+ // md5hash needs the secret appended
+ $data .= $secret;
+ return md5($data);
+ break;
+ case 'md5':
+ case 'sha1':
+ case 'sha256':
+ case 'sha512':
+ return hash_hmac($signatureMethod, $data, $secret);
+ break;
+ default:
+ throw new Exception('Unknown signature algorithm: '.$signatureMethod.'. Expected: md5hash, md5, sha1, sha256, or sha512');
+ }
+ }
+
+ /**
+ * Get the original parameters.
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Get the signature for the parameters.
+ *
+ * @return string
+ */
+ public function getSignature()
+ {
+ return $this->signed['sig'];
+ }
+
+ /**
+ * Get a full set of parameters including the signature and timestamp.
+ *
+ * @return array
+ */
+ public function getSignedParams()
+ {
+ return $this->signed;
+ }
+
+ /**
+ * Check that a signature (or set of parameters) is valid.
+ *
+ * @param array| string $signature
+ * @return bool
+ * @throws \InvalidArgumentException
+ */
+ public function check($signature)
+ {
+ if(is_array($signature) AND isset($signature['sig'])){
+ $signature = $signature['sig'];
+ }
+
+ if(!is_string($signature)){
+ throw new \InvalidArgumentException('signature must be string, or present in array or parameters');
+ }
+
+ return $signature == $this->signed['sig'];
+ }
+
+ /**
+ * Allow easy comparison.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getSignature();
+ }
+}
diff --git a/vendor/nexmo/client/src/Conversations/Collection.php b/vendor/nexmo/client/src/Conversations/Collection.php
new file mode 100644
index 000000000..3ab3615bc
--- /dev/null
+++ b/vendor/nexmo/client/src/Conversations/Collection.php
@@ -0,0 +1,178 @@
+setClient($this->getClient());
+ $idOrConversation->jsonUnserialize($data);
+
+ return $idOrConversation;
+ }
+
+ public function hydrateAll($conversations)
+ {
+ $hydrated = [];
+ foreach ($conversations as $conversation) {
+ $hydrated[] = $this->hydrateEntity($conversation, $conversation['id']);
+ }
+
+ return $hydrated;
+ }
+
+ /**
+ * @param null $conversation
+ * @return $this|Conversation
+ */
+ public function __invoke(Filter $filter = null)
+ {
+ if(!is_null($filter)){
+ $this->setFilter($filter);
+ }
+
+ return $this;
+ }
+
+ public function create($conversation)
+ {
+ return $this->post($conversation);
+ }
+
+ public function post($conversation)
+ {
+ if($conversation instanceof Conversation){
+ $body = $conversation->getRequestData();
+ } else {
+ $body = $conversation;
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $this->getCollectionPath()
+ ,'POST',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($body));
+ $response = $this->getClient()->send($request);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $body = json_decode($response->getBody()->getContents(), true);
+ $conversation = new Conversation($body['id']);
+ $conversation->jsonUnserialize($body);
+ $conversation->setClient($this->getClient());
+
+ return $conversation;
+ }
+
+ public function get($conversation)
+ {
+ if(!($conversation instanceof Conversation)){
+ $conversation = new Conversation($conversation);
+ }
+
+ $conversation->setClient($this->getClient());
+ $conversation->get();
+
+ return $conversation;
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ // This message isn't very useful, but we shouldn't ever see it
+ $errorTitle = 'Unexpected error';
+
+ if (isset($body['description'])) {
+ $errorTitle = $body['description'];
+ }
+
+ if (isset($body['error_title'])) {
+ $errorTitle = $body['error_title'];
+ }
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($errorTitle, $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($errorTitle, $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+ public function offsetExists($offset)
+ {
+ return true;
+ }
+
+ /**
+ * @param mixed $conversation
+ * @return Conversation
+ */
+ public function offsetGet($conversation)
+ {
+ if(!($conversation instanceof Conversation)){
+ $conversation = new Conversation($conversation);
+ }
+
+ $conversation->setClient($this->getClient());
+ return $conversation;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new \RuntimeException('can not set collection properties');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new \RuntimeException('can not unset collection properties');
+ }
+}
diff --git a/vendor/nexmo/client/src/Conversations/Conversation.php b/vendor/nexmo/client/src/Conversations/Conversation.php
new file mode 100644
index 000000000..70d59b502
--- /dev/null
+++ b/vendor/nexmo/client/src/Conversations/Conversation.php
@@ -0,0 +1,182 @@
+data['id'] = $id;
+ }
+
+ public function setName($name)
+ {
+ $this->data['name'] = $name;
+ return $this;
+ }
+
+ public function setDisplayName($name)
+ {
+ $this->data['display_name'] = $name;
+ return $this;
+ }
+
+ public function getId()
+ {
+ if (isset($this->data['uuid'])) {
+ return $this->data['uuid'];
+ }
+ return $this->data['id'];
+ }
+
+ public function __toString()
+ {
+ return (string)$this->getId();
+ }
+
+
+ public function get()
+ {
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId()
+ ,'GET'
+ );
+
+ $response = $this->getClient()->send($request);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $data = json_decode($response->getBody()->getContents(), true);
+ $this->jsonUnserialize($data);
+
+ return $this;
+ }
+
+
+ public function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ $this->data = $json;
+ }
+
+ public function members()
+ {
+ $response = $this->getClient()->get($this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId() .'/members');
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $data = json_decode($response->getBody()->getContents(), true);
+ $memberCollection = new UserCollection();
+ return $memberCollection->hydrateAll($data);
+ }
+
+ public function addMember(User $user)
+ {
+ return $this->sendPostAction($user, 'join');
+ }
+
+ public function inviteMember(User $user)
+ {
+ return $this->sendPostAction($user, 'invite');
+ }
+
+ public function removeMember(User $user)
+ {
+ $response = $this->getClient()->delete(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId() .'/members/'. $user->getId()
+ );
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+ }
+
+ public function sendPostAction(User $user, $action, $channel = 'app') {
+ $body = $user->getRequestDataForConversation();
+ $body['action'] = $action;
+ $body['channel'] = ['type' => $channel];
+
+ $response = $this->getClient()->post(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId() .'/members',
+ $body
+ );
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $body = json_decode($response->getBody()->getContents(), true);
+
+ $user = new User($body['user_id']);
+ $user->jsonUnserialize($body);
+ $user->setClient($this->getClient());
+
+ return $user;
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ // This message isn't very useful, but we shouldn't ever see it
+ $errorTitle = 'Unexpected error';
+
+ if (isset($body['description'])) {
+ $errorTitle = $body['description'];
+ }
+
+ if (isset($body['error_title'])) {
+ $errorTitle = $body['error_title'];
+ }
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($errorTitle, $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($errorTitle, $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+
+}
diff --git a/vendor/nexmo/client/src/Conversion/Client.php b/vendor/nexmo/client/src/Conversion/Client.php
new file mode 100644
index 000000000..691401fbb
--- /dev/null
+++ b/vendor/nexmo/client/src/Conversion/Client.php
@@ -0,0 +1,63 @@
+sendConversion('sms', $message_id, $delivered, $timestamp);
+ }
+
+ public function voice($message_id, $delivered, $timestamp=null)
+ {
+ return $this->sendConversion('voice', $message_id, $delivered, $timestamp);
+ }
+
+ protected function sendConversion($type, $message_id, $delivered, $timestamp=null)
+ {
+ $params = [
+ 'message-id' => $message_id,
+ 'delivered' => $delivered
+ ];
+
+ if ($timestamp) {
+ $params['timestamp'] = $timestamp;
+ }
+
+ $response = $this->client->postUrlEncoded(
+ $this->getClient()->getApiUrl() . '/conversions/'.$type.'?'.http_build_query($params),
+ []
+ );
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status === 402) {
+ $e = new Exception\Request("This endpoint may need activating on your account. Please email support@nexmo.com for more information", $status);
+ } elseif($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error_title'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error_title'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ }
+
+ return $e;
+ }
+
+}
diff --git a/vendor/nexmo/client/src/Entity/ArrayAccessTrait.php b/vendor/nexmo/client/src/Entity/ArrayAccessTrait.php
new file mode 100644
index 000000000..d8025dad1
--- /dev/null
+++ b/vendor/nexmo/client/src/Entity/ArrayAccessTrait.php
@@ -0,0 +1,16 @@
+collection = $collection;
+ }
+
+ public function getCollection()
+ {
+ if(!isset($this->collection)){
+ throw new \RuntimeException('missing collection');
+ }
+
+ return $this->collection;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Entity/CollectionInterface.php b/vendor/nexmo/client/src/Entity/CollectionInterface.php
new file mode 100644
index 000000000..111b71d53
--- /dev/null
+++ b/vendor/nexmo/client/src/Entity/CollectionInterface.php
@@ -0,0 +1,30 @@
+hydrateEntity($this->page['_embedded'][$this->getCollectionName()][$this->current], $this->key());
+ }
+
+ /**
+ * No checks here, just advance the index.
+ */
+ public function next()
+ {
+ $this->current++;
+ }
+
+ /**
+ * Return the ID of the resource, in some cases this is `id`, in others `uuid`.
+ * @return string
+ */
+ public function key()
+ {
+ if(isset($this->page['_embedded'][$this->getCollectionName()][$this->current]['id'])){
+ return $this->page['_embedded'][$this->getCollectionName()][$this->current]['id'];
+ } elseif(isset($this->page['_embedded'][$this->getCollectionName()][$this->current]['uuid'])) {
+ return $this->page['_embedded'][$this->getCollectionName()][$this->current]['uuid'];
+ }
+
+ return $this->current;
+ }
+
+ /**
+ * Handle pagination automatically (unless configured not to).
+ * @return bool
+ */
+ public function valid()
+ {
+ //can't be valid if there's not a page (rewind sets this)
+ if(!isset($this->page)){
+ return false;
+ }
+
+ //all hal collections have an `_embedded` object, we expect there to be a property matching the collection name
+ if(!isset($this->page['_embedded']) OR !isset($this->page['_embedded'][$this->getCollectionName()])){
+ return false;
+ }
+
+ //if we have a page with no items, we've gone beyond the end of the collection
+ if(!count($this->page['_embedded'][$this->getCollectionName()])){
+ return false;
+ }
+
+ //index the start of a page at 0
+ if(is_null($this->current)){
+ $this->current = 0;
+ }
+
+ //if our current index is past the current page, fetch the next page if possible and reset the index
+ if(!isset($this->page['_embedded'][$this->getCollectionName()][$this->current])){
+ if(isset($this->page['_links']) AND isset($this->page['_links']['next'])){
+ $this->fetchPage($this->page['_links']['next']['href']);
+ $this->current = 0;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Fetch the initial page
+ */
+ public function rewind()
+ {
+ $this->fetchPage($this->getCollectionPath());
+ }
+
+ /**
+ * Count of total items
+ * @return integer
+ */
+ public function count()
+ {
+ if(isset($this->page)){
+ return (int) $this->page['count'];
+ }
+ }
+
+ public function setPage($index)
+ {
+ $this->index = (int) $index;
+ return $this;
+ }
+
+ public function getPage()
+ {
+ if(isset($this->page)){
+ return $this->page['page_index'];
+ }
+
+ if(isset($this->index)){
+ return $this->index;
+ }
+
+ throw new \RuntimeException('page not set');
+ }
+
+ public function getSize()
+ {
+ if(isset($this->page)){
+ return $this->page['page_size'];
+ }
+
+ if(isset($this->size)){
+ return $this->size;
+ }
+
+ throw new \RuntimeException('size not set');
+ }
+
+ public function setSize($size)
+ {
+ $this->size = (int) $size;
+ return $this;
+ }
+
+ /**
+ * Filters reduce to query params and include paging settings.
+ *
+ * @param FilterInterface $filter
+ * @return $this
+ */
+ public function setFilter(FilterInterface $filter)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ public function getFilter()
+ {
+ if(!isset($this->filter)){
+ $this->setFilter(new EmptyFilter());
+ }
+
+ return $this->filter;
+ }
+
+ /**
+ * Fetch a page using the current filter if no query is provided.
+ *
+ * @param $absoluteUri
+ */
+ protected function fetchPage($absoluteUri)
+ {
+ //use filter if no query provided
+ if(false === strpos($absoluteUri, '?')){
+ $query = [];
+
+ if(isset($this->size)){
+ $query['page_size'] = $this->size;
+ }
+
+ if(isset($this->index)){
+ $query['page_index'] = $this->index;
+ }
+
+ if(isset($this->filter)){
+ $query = array_merge($this->filter->getQuery(), $query);
+ }
+
+ $absoluteUri .= '?' . http_build_query($query);
+ }
+
+ //
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $absoluteUri,
+ 'GET'
+ );
+
+ $response = $this->client->send($request);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $this->response = $response;
+ $this->page = json_decode($this->response->getBody()->getContents(), true);
+ }
+}
diff --git a/vendor/nexmo/client/src/Entity/EmptyFilter.php b/vendor/nexmo/client/src/Entity/EmptyFilter.php
new file mode 100644
index 000000000..f66b891a8
--- /dev/null
+++ b/vendor/nexmo/client/src/Entity/EmptyFilter.php
@@ -0,0 +1,18 @@
+entity = $entity;
+ }
+
+ public function getEntity()
+ {
+ return $this->entity;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Entity/JsonResponseTrait.php b/vendor/nexmo/client/src/Entity/JsonResponseTrait.php
new file mode 100644
index 000000000..eca56f130
--- /dev/null
+++ b/vendor/nexmo/client/src/Entity/JsonResponseTrait.php
@@ -0,0 +1,39 @@
+getResponse()) && ($response instanceof ResponseInterface)){
+ if($response->getBody()->isSeekable()){
+ $response->getBody()->rewind();
+ }
+
+ $body = $response->getBody()->getContents();
+ $this->responseJson = json_decode($body, true);
+ return $this->responseJson;
+ }
+
+ return [];
+ }
+}
diff --git a/vendor/nexmo/client/src/Entity/JsonSerializableInterface.php b/vendor/nexmo/client/src/Entity/JsonSerializableInterface.php
new file mode 100644
index 000000000..6914cddc0
--- /dev/null
+++ b/vendor/nexmo/client/src/Entity/JsonSerializableInterface.php
@@ -0,0 +1,14 @@
+getRequest())){
+ //TODO, figure out what the request data actually was
+ }
+
+ return $this->jsonSerialize();
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Entity/JsonUnserializableInterface.php b/vendor/nexmo/client/src/Entity/JsonUnserializableInterface.php
new file mode 100644
index 000000000..aa64c02af
--- /dev/null
+++ b/vendor/nexmo/client/src/Entity/JsonUnserializableInterface.php
@@ -0,0 +1,22 @@
+response = $response;
+
+ $status = $response->getStatusCode();
+
+ if($this instanceof JsonUnserializableInterface AND ((200 == $status) OR (201 == $status))){
+ $this->jsonUnserialize($this->getResponseData());
+ }
+ }
+
+ public function setRequest(\Psr\Http\Message\RequestInterface $request)
+ {
+ $this->request = $request;
+ }
+
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ public function getResponse()
+ {
+ return $this->response;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Entity/RequestArrayTrait.php b/vendor/nexmo/client/src/Entity/RequestArrayTrait.php
new file mode 100644
index 000000000..8d36c4081
--- /dev/null
+++ b/vendor/nexmo/client/src/Entity/RequestArrayTrait.php
@@ -0,0 +1,74 @@
+getRequest())){
+ $query = [];
+ parse_str($request->getUri()->getQuery(), $query);
+ return $query;
+ }
+
+ // Trigger a pre-getRequestData() hook for any last minute
+ // decision making that needs to be done, but only if
+ // it hasn't been sent already
+ if (method_exists($this, 'preGetRequestDataHook')) {
+ $this->preGetRequestDataHook();
+ }
+
+ return $this->requestData;
+ }
+
+ protected function setRequestData($name, $value)
+ {
+ if(!($this instanceof EntityInterface)){
+ throw new \Exception(sprintf(
+ '%s can only be used if the class implements %s',
+ __TRAIT__,
+ EntityInterface::class
+ ));
+ }
+
+ if($this->getResponse()){
+ throw new \RuntimeException(sprintf(
+ 'can not set request parameter `%s` for `%s` after API request has be made',
+ $name,
+ get_class($this)
+ ));
+ }
+
+ $this->requestData[$name] = $value;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Insights/Advanced.php b/vendor/nexmo/client/src/Insights/Advanced.php
new file mode 100644
index 000000000..83e114428
--- /dev/null
+++ b/vendor/nexmo/client/src/Insights/Advanced.php
@@ -0,0 +1,15 @@
+data['national_format_number'] = $number;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRequestId()
+ {
+ return $this['request_id'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getNationalFormatNumber()
+ {
+ return $this['national_format_number'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getInternationalFormatNumber()
+ {
+ return $this['international_format_number'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getCountryCode()
+ {
+ return $this['country_code'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getCountryCodeISO3()
+ {
+ return $this['country_code_iso3'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getCountryName()
+ {
+ return $this['country_name'];
+ }
+
+ /**
+ * @return integer
+ */
+ public function getCountryPrefix()
+ {
+ return $this['country_prefix'];
+ }
+
+ public function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ $this->data = $json;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new Exception('Number insights results are read only');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new Exception('Number insights results are read only');
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Insights/Client.php b/vendor/nexmo/client/src/Insights/Client.php
new file mode 100644
index 000000000..a35297960
--- /dev/null
+++ b/vendor/nexmo/client/src/Insights/Client.php
@@ -0,0 +1,118 @@
+makeRequest('/ni/basic/json', $number);
+
+ $basic = new Basic($insightsResults['national_format_number']);
+ $basic->jsonUnserialize($insightsResults);
+ return $basic;
+ }
+
+ public function standardCNam($number)
+ {
+ $insightsResults = $this->makeRequest('/ni/standard/json', $number, ['cnam' => 'true']);
+ $standard = new StandardCnam($insightsResults['national_format_number']);
+ $standard->jsonUnserialize($insightsResults);
+ return $standard;
+ }
+
+ public function advancedCnam($number)
+ {
+ $insightsResults = $this->makeRequest('/ni/advanced/json', $number, ['cnam' => 'true']);
+ $standard = new AdvancedCnam($insightsResults['national_format_number']);
+ $standard->jsonUnserialize($insightsResults);
+ return $standard;
+ }
+
+ public function standard($number, $useCnam=false)
+ {
+ $insightsResults = $this->makeRequest('/ni/standard/json', $number);
+ $standard = new Standard($insightsResults['national_format_number']);
+ $standard->jsonUnserialize($insightsResults);
+ return $standard;
+ }
+
+ public function advanced($number)
+ {
+ $insightsResults = $this->makeRequest('/ni/advanced/json', $number);
+ $advanced = new Advanced($insightsResults['national_format_number']);
+ $advanced->jsonUnserialize($insightsResults);
+ return $advanced;
+ }
+
+ public function advancedAsync($number, $webhook)
+ {
+ // This method does not have a return value as it's async. If there is no exception thrown
+ // We can assume that everything is fine
+ $this->makeRequest('/ni/advanced/async/json', $number, ['callback' => $webhook]);
+ }
+
+ public function makeRequest($path, $number, $additionalParams = [])
+ {
+ if ($number instanceof Number)
+ {
+ $number = $number->getMsisdn();
+ }
+
+ $queryString = http_build_query([
+ 'number' => $number,
+ ] + $additionalParams);
+
+ $request = new Request(
+ $this->getClient()->getApiUrl(). $path.'?'.$queryString,
+ 'GET',
+ 'php://temp',
+ [
+ 'Accept' => 'application/json'
+ ]
+ );
+
+ $response = $this->client->send($request);
+
+ $insightsResults = json_decode($response->getBody()->getContents(), true);
+
+ if('200' != $response->getStatusCode()){
+ throw $this->getException($response);
+ }
+
+ return $insightsResults;
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error-code-label'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error-code-label'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+}
diff --git a/vendor/nexmo/client/src/Insights/CnamTrait.php b/vendor/nexmo/client/src/Insights/CnamTrait.php
new file mode 100644
index 000000000..5bd136a24
--- /dev/null
+++ b/vendor/nexmo/client/src/Insights/CnamTrait.php
@@ -0,0 +1,25 @@
+enableEncodingDetection();
+ $this->requestData['text'] = (string) $text;
+ }
+}
diff --git a/vendor/nexmo/client/src/Message/Binary.php b/vendor/nexmo/client/src/Message/Binary.php
new file mode 100644
index 000000000..ab5ab1916
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Binary.php
@@ -0,0 +1,55 @@
+body = (string) $body;
+ $this->udh = (string) $udh;
+ }
+
+ /**
+ * Get an array of params to use in an API request.
+ */
+ public function getRequestData($sent = true)
+ {
+ return array_merge(parent::getRequestData($sent), array(
+ 'body' => $this->body,
+ 'udh' => $this->udh,
+ ));
+ }
+}
diff --git a/vendor/nexmo/client/src/Message/Callback/Receipt.php b/vendor/nexmo/client/src/Message/Callback/Receipt.php
new file mode 100644
index 000000000..b884cc157
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Callback/Receipt.php
@@ -0,0 +1,139 @@
+ null), $data);
+
+ parent::__construct($data);
+ }
+
+ /**
+ * @return int
+ */
+ public function getErrorCode()
+ {
+ return (int) $this->data['err-code'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getNetwork()
+ {
+ return (string) $this->data['network-code'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getId()
+ {
+ return (string) $this->data['messageId'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getReceiptFrom()
+ {
+ return (string) $this->data['msisdn'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getTo()
+ {
+ return $this->getReceiptFrom();
+ }
+
+ /**
+ * @return string
+ */
+ public function getReceiptTo()
+ {
+ return (string) $this->data['to'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getFrom()
+ {
+ return $this->getReceiptTo();
+ }
+
+ /**
+ * @return string
+ */
+ public function getStatus()
+ {
+ return (string) $this->data['status'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getPrice()
+ {
+ return (string) $this->data['price'];
+ }
+
+ /**
+ * @return \DateTime
+ */
+ public function getTimestamp()
+ {
+ $date = \DateTime::createFromFormat('ymdHi', $this->data['scts']);
+ if($date){
+ return $date;
+ }
+
+ throw new \UnexpectedValueException('could not parse message timestamp');
+ }
+
+ /**
+ * @return \DateTime
+ */
+ public function getSent()
+ {
+ $date = \DateTime::createFromFormat('Y-m-d H:i:s', $this->data['message-timestamp']);
+ if($date){
+ return $date;
+ }
+
+ throw new \UnexpectedValueException('could not parse message timestamp');
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getClientRef()
+ {
+ return $this->data['client-ref'];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Message/Client.php b/vendor/nexmo/client/src/Message/Client.php
new file mode 100644
index 000000000..7a137ce4f
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Client.php
@@ -0,0 +1,324 @@
+createMessageFromArray($message);
+ }
+
+ $params = $message->getRequestData(false);
+
+ $request = new Request(
+ $this->getClient()->getRestUrl() . '/sms/json'
+ ,'POST',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($params));
+ $message->setRequest($request);
+ $response = $this->client->send($request);
+ $message->setResponse($response);
+
+ //check for valid data, as well as an error response from the API
+ $data = $message->getResponseData();
+ if(!isset($data['messages'])){
+ throw new Exception\Exception('unexpected response from API');
+ }
+
+ //normalize errors (client vrs server)
+ foreach($data['messages'] as $part){
+ switch($part['status']){
+ case '0':
+ break; //all okay
+ case '1':
+ if(preg_match('#\[\s+(\d+)\s+\]#', $part['error-text'], $match)){
+ usleep($match[1] + 1);
+ } else {
+ sleep(1);
+ }
+
+ return $this->send($message);
+ case '5':
+ $e = new Exception\Server($part['error-text'], $part['status']);
+ $e->setEntity($message);
+ throw $e;
+ default:
+ $e = new Exception\Request($part['error-text'], $part['status']);
+ $e->setEntity($message);
+ throw $e;
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param $query
+ * @return MessageInterface[]
+ * @throws Exception\Exception
+ * @throws Exception\Request
+ */
+ public function get($query)
+ {
+ if($query instanceof Query){
+ $params = $query->getParams();
+ } else if($query instanceof MessageInterface){
+ $params = ['ids' => [$query->getMessageId()]];
+ } else if(is_string($query)) {
+ $params = ['ids' => [$query]];
+ } else if(is_array($query)){
+ $params = ['ids' => $query];
+ } else {
+ throw new \InvalidArgumentException('query must be an instance of Query, MessageInterface, string ID, or array of IDs.');
+ }
+
+ $request = new Request(
+ $this->getClient()->getRestUrl() . '/search/messages?' . http_build_query($params),
+ 'GET',
+ 'php://temp',
+ ['Accept' => 'application/json']
+ );
+
+ $response = $this->client->send($request);
+ $response->getBody()->rewind();
+ $data = json_decode($response->getBody()->getContents(), true);
+
+ if($response->getStatusCode() != '200' && isset($data['error-code'])){
+ throw new Exception\Request($data['error-code-label'], $data['error-code']);
+ } elseif($response->getStatusCode() != '200'){
+ throw new Exception\Request('error status from API', $response->getStatusCode());
+ }
+
+ if(!isset($data['items'])){
+ throw new Exception\Exception('unexpected response from API');
+ }
+
+ if(count($data['items']) == 0){
+ return [];
+ }
+
+ $collection = [];
+
+ foreach($data['items'] as $index => $item){
+ switch($item['type']){
+ case 'MT':
+ $new = new Message($item['message-id']);
+ break;
+ case 'MO':
+ $new = new InboundMessage($item['message-id']);
+ break;
+ default:
+ throw new Exception\Exception('unexpected response from API');
+ }
+
+ $new->setResponse($response);
+ $new->setIndex($index);
+ $collection[] = $new;
+
+ }
+
+ return $collection;
+ }
+
+ /**
+ * @param string|MessageInterface $idOrMessage
+ */
+ public function search($idOrMessage)
+ {
+ if($idOrMessage instanceof MessageInterface){
+ $id = $idOrMessage->getMessageId();
+ $message = $idOrMessage;
+ } else {
+ $id = $idOrMessage;
+ }
+
+ $request = new Request(
+ $this->getClient()->getRestUrl() . '/search/message?' . http_build_query(['id' => $id]),
+ 'GET',
+ 'php://temp',
+ ['Accept' => 'application/json']
+ );
+
+ $response = $this->client->send($request);
+
+ $response->getBody()->rewind();
+
+ $data = json_decode($response->getBody()->getContents(), true);
+
+ if($response->getStatusCode() != '200' && isset($data['error-code'])){
+ throw new Exception\Request($data['error-code-label'], $data['error-code']);
+ } elseif($response->getStatusCode() == '429'){
+ throw new Exception\Request('too many concurrent requests', $response->getStatusCode());
+ } elseif($response->getStatusCode() != '200'){
+ throw new Exception\Request('error status from API', $response->getStatusCode());
+ }
+
+ if(!$data){
+ throw new Exception\Request('no message found for `' . $id . '`');
+ }
+
+ switch($data['type']){
+ case 'MT':
+ $new = new Message($data['message-id']);
+ break;
+ case 'MO':
+ $new = new InboundMessage($data['message-id']);
+ break;
+ default:
+ throw new Exception\Exception('unexpected response from API');
+ }
+
+ if(isset($message) && !($message instanceof $new)){
+ throw new Exception\Exception(sprintf(
+ 'searched for message with type `%s` but message of type `%s`',
+ get_class($message),
+ get_class($new)
+ ));
+ }
+
+ if(!isset($message)){
+ $message = $new;
+ }
+
+ $message->setResponse($response);
+ return $message;
+ }
+
+ public function searchRejections(Query $query) {
+
+ $params = $query->getParams();
+ $request = new Request(
+ $this->getClient()->getRestUrl() . '/search/rejections?' . http_build_query($params),
+ 'GET',
+ 'php://temp',
+ ['Accept' => 'application/json']
+ );
+
+ $response = $this->client->send($request);
+ $response->getBody()->rewind();
+ $data = json_decode($response->getBody()->getContents(), true);
+
+ if($response->getStatusCode() != '200' && isset($data['error-code'])){
+ throw new Exception\Request($data['error-code-label'], $data['error-code']);
+ } elseif($response->getStatusCode() != '200'){
+ throw new Exception\Request('error status from API', $response->getStatusCode());
+ }
+
+ if(!isset($data['items'])){
+ throw new Exception\Exception('unexpected response from API');
+ }
+
+ if(count($data['items']) == 0){
+ return [];
+ }
+
+ $collection = [];
+
+ foreach($data['items'] as $index => $item){
+ switch($item['type']){
+ case 'MT':
+ $new = new Message($item['message-id']);
+ break;
+ case 'MO':
+ $new = new InboundMessage($item['message-id']);
+ break;
+ default:
+ throw new Exception\Exception('unexpected response from API');
+ }
+
+ $new->setResponse($response);
+ $new->setIndex($index);
+ $collection[] = $new;
+ }
+
+ return $collection;
+ }
+
+ /**
+ * @param array $message
+ * @return Message
+ */
+ protected function createMessageFromArray($message)
+ {
+ if(!is_array($message)){
+ throw new \RuntimeException('message must implement `' . MessageInterface::class . '` or be an array`');
+ }
+
+ foreach(['to', 'from'] as $param){
+ if(!isset($message[$param])){
+ throw new \InvalidArgumentException('missing expected key `' . $param . '`');
+ }
+ }
+
+ $to = $message['to'];
+ $from = $message['from'];
+
+ unset($message['to']);
+ unset($message['from']);
+
+ return new Message($to, $from, $message);
+ }
+
+ /**
+ * Convenience feature allowing messages to be sent without creating a message object first.
+ *
+ * @param $name
+ * @param $arguments
+ * @return MessageInterface
+ */
+ public function __call($name, $arguments)
+ {
+ if(!(strstr($name, 'send') !== 0)){
+ throw new \RuntimeException(sprintf(
+ '`%s` is not a valid method on `%s`',
+ $name,
+ get_class($this)
+ ));
+ }
+
+ $class = substr($name, 4);
+ $class = 'Nexmo\\Message\\' . ucfirst(strtolower($class));
+
+ if(!class_exists($class)){
+ throw new \RuntimeException(sprintf(
+ '`%s` is not a valid method on `%s`',
+ $name,
+ get_class($this)
+ ));
+ }
+
+ $reflection = new \ReflectionClass($class);
+ $message = $reflection->newInstanceArgs($arguments);
+
+ return $this->send($message);
+ }
+}
diff --git a/vendor/nexmo/client/src/Message/CollectionTrait.php b/vendor/nexmo/client/src/Message/CollectionTrait.php
new file mode 100644
index 000000000..c3a35ba4b
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/CollectionTrait.php
@@ -0,0 +1,12 @@
+index = (int) $index;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Message/EncodingDetector.php b/vendor/nexmo/client/src/Message/EncodingDetector.php
new file mode 100644
index 000000000..4ee6b099f
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/EncodingDetector.php
@@ -0,0 +1,53 @@
+convertIntoUnicode(),
+ [ // See: https://en.wikipedia.org/wiki/GSM_03.38#GSM_7-bit_default_alphabet_and_extension_table_of_3GPP_TS_23.038_/_GSM_03.38
+ '@', '£', '$', '¥', 'è', 'é', 'ù', 'ì', 'ò', 'ç', "\r", 'Ø', 'ø', "\n", 'Å', 'å',
+ 'Δ', '_', 'Φ', 'Γ', 'Λ', 'Ω', 'Π', 'Ψ', 'Σ', 'Θ', 'Ξ', 'Æ', 'æ', 'ß', 'É',
+ ' ', '!', '"', '#', '¤', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
+ '¡', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ä', 'Ö', 'Ñ', 'Ü', '§',
+ '¿', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'ä', 'ö', 'ñ', 'ü', 'à',
+ "\f", '^', '{', '}', '\\', '[', '~', ']', '|', '€',
+ ]
+ );
+
+ // Split $text into an array in a way that respects multibyte characters.
+ $textChars = preg_split('//u', $content, null, PREG_SPLIT_NO_EMPTY);
+
+ // Array of codepoint values for characters in $text.
+ $textCodePoints = array_map($this->convertIntoUnicode(), $textChars);
+
+ // Filter the array to contain only codepoints from $text that are not in the set of valid GSM codepoints.
+ $nonGsmCodePoints = array_diff($textCodePoints, $gsmCodePoints);
+
+ // The text contains unicode if the result is not empty.
+ return !empty($nonGsmCodePoints);
+ }
+
+ private function convertIntoUnicode()
+ {
+ return function ($char) {
+ $k = mb_convert_encoding($char, 'UTF-16LE', 'UTF-8');
+ $k1 = ord(substr($k, 0, 1));
+ $k2 = ord(substr($k, 1, 1));
+
+ return $k2 * 256 + $k1;
+ };
+ }
+}
diff --git a/vendor/nexmo/client/src/Message/InboundMessage.php b/vendor/nexmo/client/src/Message/InboundMessage.php
new file mode 100644
index 000000000..eae552900
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/InboundMessage.php
@@ -0,0 +1,237 @@
+setRequest($idOrRequest);
+ return;
+ }
+
+ if(is_string($idOrRequest)){
+ $this->id = $idOrRequest;
+ return;
+ }
+
+ throw new \RuntimeException(sprintf(
+ '`%s` must be constructed with a server request or a message id',
+ self::class
+ ));
+ }
+
+ public static function createFromGlobals()
+ {
+ $serverRequest = \Zend\Diactoros\ServerRequestFactory::fromGlobals();
+ return new self($serverRequest);
+ }
+
+ /**
+ * Create a matching reply to the inbound message. Currently only supports text replies.
+ *
+ * @param string $body
+ * @return Text
+ */
+ public function createReply($body)
+ {
+ return new Text($this->getFrom(), $this->getTo(), $body);
+ }
+
+ public function getRequestData($sent = true)
+ {
+ $request = $this->getRequest();
+
+ if(is_null($request)){
+ return [];
+ }
+
+ if(!($request instanceof ServerRequestInterface)){
+ throw new \RuntimeException('inbound message request should only ever be `' . ServerRequestInterface::class . '`');
+ }
+
+ // Check our incoming content type
+ $isApplicationJson = false;
+ $contentTypes = $request->getHeader('Content-Type');
+ // We only respect application/json if it's the first entry without any preference weighting
+ // as that's what Nexmo send
+ if (count($contentTypes) && $contentTypes[0] === 'application/json') {
+ $isApplicationJson = true;
+ }
+
+ switch($request->getMethod()){
+ case 'POST':
+ $params = $isApplicationJson ? json_decode((string)$request->getBody(), true) : $request->getParsedBody();
+ break;
+ case 'GET':
+ $params = $request->getQueryParams();
+ break;
+ default:
+ $params = [];
+ break;
+ }
+
+ return $params;
+ }
+
+ public function getFrom()
+ {
+ if($this->getRequest()){
+ return $this['msisdn'];
+ } else {
+ return $this['from'];
+ }
+ }
+
+ public function getTo()
+ {
+ return $this['to'];
+ }
+
+ public function getMessageId()
+ {
+ if(isset($this->id)){
+ return $this->id;
+ }
+
+ return $this['messageId'];
+ }
+
+ public function isValid()
+ {
+ return (bool) $this->getMessageId();
+ }
+
+ public function getBody()
+ {
+ if($this->getRequest()){
+ return $this['text'];
+ } else {
+ return $this['body'];
+ }
+ }
+
+ public function getType()
+ {
+ return $this['type'];
+ }
+
+ public function getAccountId()
+ {
+ return $this['account-id'];
+ }
+
+ public function getNetwork()
+ {
+ return $this['network'];
+ }
+
+ /**
+ * Allow the object to access the data from the API response, a sent API request, or the user set data that the
+ * request will be created from - in that order.
+ *
+ * @param mixed $offset
+ * @return bool
+ * @throws \Exception
+ */
+ public function offsetExists($offset)
+ {
+ $response = $this->getResponseData();
+
+ if(isset($this->index)){
+ $response = $response['items'][$this->index];
+ }
+
+ $request = $this->getRequestData();
+ $dirty = $this->getRequestData(false);
+ return isset($response[$offset]) || isset($request[$offset]) || isset($dirty[$offset]);
+ }
+
+ /**
+ * Allow the object to access the data from the API response, a sent API request, or the user set data that the
+ * request will be created from - in that order.
+ *
+ * @param mixed $offset
+ * @return mixed
+ * @throws \Exception
+ */
+ public function offsetGet($offset)
+ {
+ $response = $this->getResponseData();
+
+ if(isset($this->index)){
+ $response = $response['items'][$this->index];
+ }
+
+ $request = $this->getRequestData();
+ $dirty = $this->getRequestData(false);
+
+ if(isset($response[$offset])){
+ return $response[$offset];
+ }
+
+ if(isset($request[$offset])){
+ return $request[$offset];
+ }
+
+ if(isset($dirty[$offset])){
+ return $dirty[$offset];
+ }
+ }
+
+ /**
+ * All properties are read only.
+ *
+ * @param mixed $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw $this->getReadOnlyException($offset);
+ }
+
+ /**
+ * All properties are read only.
+ *
+ * @param mixed $offset
+ */
+ public function offsetUnset($offset)
+ {
+ throw $this->getReadOnlyException($offset);
+ }
+
+ /**
+ * All properties are read only.
+ *
+ * @param $offset
+ * @return \RuntimeException
+ */
+ protected function getReadOnlyException($offset)
+ {
+ return new \RuntimeException(sprintf(
+ 'can not modify `%s` using array access',
+ $offset
+ ));
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Message/Message.php b/vendor/nexmo/client/src/Message/Message.php
new file mode 100644
index 000000000..fac8f6175
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Message.php
@@ -0,0 +1,375 @@
+id = $idOrTo;
+ return;
+ }
+
+ $this->requestData['to'] = (string) $idOrTo;
+ $this->requestData['from'] = (string) $from;
+ if(static::TYPE){
+ $this->requestData['type'] = static::TYPE;
+ }
+
+ $this->requestData = array_merge($this->requestData, $additional);
+ }
+
+ public function requestDLR($dlr = true)
+ {
+ return $this->setRequestData('status-report-req', $dlr ? 1 : 0);
+ }
+
+ public function setCallback($callback) {
+ return $this->setRequestData('callback', (string) $callback);
+ }
+
+ public function setClientRef($ref)
+ {
+ return $this->setRequestData('client-ref', (string) $ref);
+ }
+
+ public function setNetwork($network)
+ {
+ return $this->setRequestData('network-code', (string) $network);
+ }
+
+ public function setTTL($ttl)
+ {
+ return $this->setRequestData('ttl', (int) $ttl);
+ }
+
+ public function setClass($class)
+ {
+ return $this->setRequestData('message-class', $class);
+ }
+
+ public function enableEncodingDetection()
+ {
+ $this->autodetectEncoding = true;
+ }
+
+ public function disableEncodingDetection()
+ {
+ $this->autodetectEncoding = false;
+ }
+
+ public function count()
+ {
+ $data = $this->getResponseData();
+ if(!isset($data['messages'])){
+ return 0;
+ }
+
+ return count($data['messages']);
+ }
+
+ public function getMessageId($index = null)
+ {
+ if(isset($this->id)){
+ return $this->id;
+ }
+
+ return $this->getMessageData('message-id', $index);
+ }
+
+ public function getStatus($index = null)
+ {
+ return $this->getMessageData('status', $index);
+ }
+
+ public function getFinalStatus($index = null)
+ {
+ return $this->getMessageData('final-status', $index);
+ }
+
+ public function getTo($index = null)
+ {
+ $data = $this->getResponseData();
+
+ //check if this is data from a send request
+ //(which also has a status, but it's not the same)
+ if(isset($data['messages'])){
+ return $this->getMessageData('to', $index);
+ }
+
+ return $this['to'];
+ }
+
+ public function getRemainingBalance($index = null)
+ {
+ return $this->getMessageData('remaining-balance', $index);
+ }
+
+ public function getPrice($index = null)
+ {
+ $data = $this->getResponseData();
+
+ //check if this is data from a send request
+ //(which also has a status, but it's not the same)
+ if(isset($data['messages'])){
+ return $this->getMessageData('message-price', $index);
+ }
+
+ return $this['price'];
+ }
+
+ public function getNetwork($index = null)
+ {
+ return $this->getMessageData('network', $index);
+ }
+
+ public function getDeliveryStatus()
+ {
+ $data = $this->getResponseData();
+
+ //check if this is data from a send request
+ //(which also has a status, but it's not the same)
+ if(isset($data['messages'])){
+ return;
+ }
+
+ return $this['status'];
+ }
+
+ public function getFrom()
+ {
+ return $this['from'];
+ }
+
+ public function getBody()
+ {
+ return $this['body'];
+ }
+
+ public function getDateReceived()
+ {
+ return new \DateTime($this['date-received']);
+ }
+
+ public function getDeliveryError()
+ {
+ return $this['error-code'];
+ }
+
+ public function getDeliveryLabel()
+ {
+ return $this['error-code-label'];
+ }
+
+ public function isEncodingDetectionEnabled()
+ {
+ return $this->autodetectEncoding;
+ }
+
+ protected function getMessageData($name, $index = null)
+ {
+ if(!isset($this->response)){
+ return null;
+ }
+
+ $data = $this->getResponseData();
+ if(is_null($index)){
+ $index = $this->count() -1;
+ }
+
+ if (isset($data['messages'])) {
+ return $data['messages'][$index][$name];
+ }
+
+ return isset($data[$name]) ? $data[$name] : null;
+ }
+
+ protected function preGetRequestDataHook()
+ {
+ // If $autodetectEncoding is true, we want to set the `type`
+ // field in our payload
+ if ($this->isEncodingDetectionEnabled()) {
+ $this->requestData['type'] = $this->detectEncoding();
+ }
+ }
+
+ protected function detectEncoding()
+ {
+ if (!isset($this->requestData['text'])) {
+ return static::TYPE;
+ }
+
+ // Auto detect unicode messages
+ $detector = new EncodingDetector;
+ if ($detector->requiresUnicodeEncoding($this->requestData['text'])){
+ return Unicode::TYPE;
+ }
+
+ return static::TYPE;
+ }
+
+ public function offsetExists($offset)
+ {
+ $response = $this->getResponseData();
+
+ if(isset($this->index)){
+ $response = $response['items'][$this->index];
+ }
+
+ $request = $this->getRequestData();
+ $dirty = $this->getRequestData(false);
+ if(isset($response[$offset]) || isset($request[$offset]) || isset($dirty[$offset])){
+ return true;
+ }
+
+ //provide access to split messages by virtual index
+ if(is_int($offset) && $offset < $this->count()){
+ return true;
+ }
+
+ return false;
+ }
+
+ public function offsetGet($offset)
+ {
+ $response = $this->getResponseData();
+
+ if(isset($this->index)){
+ $response = $response['items'][$this->index];
+ }
+
+ $request = $this->getRequestData();
+ $dirty = $this->getRequestData(false);
+
+ if(isset($response[$offset])){
+ return $response[$offset];
+ }
+
+ //provide access to split messages by virtual index, if there is data
+ if(isset($response['messages'])){
+ if(is_int($offset) && isset($response['messages'][$offset])){
+ return $response['messages'][$offset];
+ }
+
+ $index = $this->count() -1;
+
+ if(isset($response['messages'][$index]) && isset($response['messages'][$index][$offset])){
+ return $response['messages'][$index][$offset];
+ }
+
+ }
+
+ if(isset($request[$offset])){
+ return $request[$offset];
+ }
+
+ if(isset($dirty[$offset])){
+ return $dirty[$offset];
+ }
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw $this->getReadOnlyException($offset);
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw $this->getReadOnlyException($offset);
+ }
+
+ protected function getReadOnlyException($offset)
+ {
+ return new \RuntimeException(sprintf(
+ 'can not modify `%s` using array access',
+ $offset
+ ));
+ }
+
+ public function current()
+ {
+ if(!isset($this->response)){
+ return null;
+ }
+
+ $data = $this->getResponseData();
+ return $data['messages'][$this->current];
+ }
+
+ public function next()
+ {
+ $this->current++;
+ }
+
+ public function key()
+ {
+ if(!isset($this->response)){
+ return null;
+ }
+
+ return $this->current;
+ }
+
+ public function valid()
+ {
+ if(!isset($this->response)){
+ return null;
+ }
+
+ $data = $this->getResponseData();
+ return isset($data['messages'][$this->current]);
+ }
+
+ public function rewind()
+ {
+ $this->current = 0;
+ }
+
+
+
+}
diff --git a/vendor/nexmo/client/src/Message/MessageInterface.php b/vendor/nexmo/client/src/Message/MessageInterface.php
new file mode 100644
index 000000000..f458e0565
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/MessageInterface.php
@@ -0,0 +1,14 @@
+params['date'] = $date->format('Y-m-d');
+ $this->params['to'] = $to;
+ }
+
+ public function getParams()
+ {
+ return $this->params;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Message/Response/Collection.php b/vendor/nexmo/client/src/Message/Response/Collection.php
new file mode 100644
index 000000000..831c5dc0f
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Response/Collection.php
@@ -0,0 +1,125 @@
+expected = array('message-count', 'messages');
+ $return = parent::__construct($data);
+
+ $this->count = $data['message-count'];
+
+ if(count($data['messages']) != $data['message-count']){
+ throw new \RuntimeException('invalid message count');
+ }
+
+ foreach($data['messages'] as $message){
+ if(0 != $message['status']){
+ $this->messages[] = new Error($message);
+ } else {
+ $this->messages[] = new Message($message);
+ }
+ }
+
+ $this->data = $data;
+
+ return $return;
+ }
+
+ public function getMessages()
+ {
+ return $this->messages;
+ }
+
+ public function isSuccess()
+ {
+ foreach($this->messages as $message){
+ if($message instanceof Error){
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function count()
+ {
+ return $this->count;
+ }
+
+ /**
+ * @link http://php.net/manual/en/iterator.current.php
+ * @return Message
+ */
+ public function current()
+ {
+ return $this->messages[$this->position];
+ }
+
+ /**
+ * @link http://php.net/manual/en/iterator.next.php
+ * @return void
+ */
+ public function next()
+ {
+ $this->position++;
+ }
+
+ /**
+ * @link http://php.net/manual/en/iterator.key.php
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * @link http://php.net/manual/en/iterator.valid.php
+ * @return boolean
+ */
+ public function valid()
+ {
+ return $this->position < $this->count;
+ }
+
+ /**
+ * @link http://php.net/manual/en/iterator.rewind.php
+ * @return void
+ */
+ public function rewind()
+ {
+ $this->position = 0;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Message/Response/Message.php b/vendor/nexmo/client/src/Message/Response/Message.php
new file mode 100644
index 000000000..7069dd40e
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Response/Message.php
@@ -0,0 +1,121 @@
+expected = array(
+ 'status',
+ 'message-id',
+ 'to',
+ 'message-price',
+ 'network'
+ );
+
+ //default value
+ $data = array_merge(array('client-ref' => null, 'remaining-balance' => null), $data);
+
+ $return = parent::__construct($data);
+
+ //validate receipt
+ if(!$receipt){
+ return $return;
+ }
+
+ if($receipt->getId() != $this->getId()){
+ throw new \UnexpectedValueException('receipt id must match message id');
+ }
+
+ $this->receipt = $receipt;
+
+ return $receipt;
+ }
+
+ /**
+ * @return int
+ */
+ public function getStatus()
+ {
+ return (int) $this->data['status'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getId()
+ {
+ return (string) $this->data['message-id'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getTo()
+ {
+ return (string) $this->data['to'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getBalance()
+ {
+ return (string) $this->data['remaining-balance'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getPrice()
+ {
+ return (string) $this->data['message-price'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getNetwork()
+ {
+ return (string) $this->data['network'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getClientRef()
+ {
+ return (string) $this->data['client-ref'];
+ }
+
+ /**
+ * @return Receipt|null
+ */
+ public function getReceipt()
+ {
+ return $this->receipt;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasReceipt()
+ {
+ return $this->receipt instanceof Receipt;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Message/Text.php b/vendor/nexmo/client/src/Message/Text.php
new file mode 100644
index 000000000..fc58a26a6
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Text.php
@@ -0,0 +1,37 @@
+requestData['text'] = (string) $text;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Message/Unicode.php b/vendor/nexmo/client/src/Message/Unicode.php
new file mode 100644
index 000000000..23e1e873d
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Unicode.php
@@ -0,0 +1,46 @@
+text = (string) $text;
+ }
+
+ /**
+ * Get an array of params to use in an API request.
+ */
+ public function getRequestData($sent = true)
+ {
+ return array_merge(parent::getRequestData($sent), array(
+ 'text' => $this->text
+ ));
+ }
+}
diff --git a/vendor/nexmo/client/src/Message/Vcal.php b/vendor/nexmo/client/src/Message/Vcal.php
new file mode 100644
index 000000000..ac55bf1c2
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Vcal.php
@@ -0,0 +1,46 @@
+text = (string) $vcal;
+ }
+
+ /**
+ * Get an array of params to use in an API request.
+ */
+ public function getRequestData($sent = true)
+ {
+ return array_merge(parent::getRequestData($sent), array(
+ 'vcal' => $this->vcal
+ ));
+ }
+}
diff --git a/vendor/nexmo/client/src/Message/Vcard.php b/vendor/nexmo/client/src/Message/Vcard.php
new file mode 100644
index 000000000..b6fdafd39
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Vcard.php
@@ -0,0 +1,46 @@
+vcard = (string) $vcard;
+ }
+
+ /**
+ * Get an array of params to use in an API request.
+ */
+ public function getRequestData($sent = true)
+ {
+ return array_merge(parent::getRequestData($sent), array(
+ 'vcard' => $this->vcard
+ ));
+ }
+}
diff --git a/vendor/nexmo/client/src/Message/Wap.php b/vendor/nexmo/client/src/Message/Wap.php
new file mode 100644
index 000000000..1c908b2b3
--- /dev/null
+++ b/vendor/nexmo/client/src/Message/Wap.php
@@ -0,0 +1,64 @@
+title = (string) $title;
+ $this->url = (string) $url;
+ $this->validity = (int) $validity;
+ }
+
+ /**
+ * Get an array of params to use in an API request.
+ */
+ public function getRequestData($sent = true)
+ {
+ return array_merge(parent::getRequestData($sent), array(
+ 'title' => $this->title,
+ 'url' => $this->url,
+ 'validity' => $this->validity,
+ ));
+ }
+}
diff --git a/vendor/nexmo/client/src/Network.php b/vendor/nexmo/client/src/Network.php
new file mode 100644
index 000000000..37aaf519b
--- /dev/null
+++ b/vendor/nexmo/client/src/Network.php
@@ -0,0 +1,97 @@
+data['network_code'] = $networkCode;
+ $this->data['network_name'] = $networkName;
+ }
+
+ public function getCode()
+ {
+ return $this['network_code'];
+ }
+
+ public function getName()
+ {
+ return $this['network_name'];
+ }
+
+ public function getOutboundSmsPrice()
+ {
+ if (isset($this['sms_price'])) {
+ return $this['sms_price'];
+ }
+ return $this['price'];
+ }
+
+ public function getOutboundVoicePrice()
+ {
+ if (isset($this['voice_price'])) {
+ return $this['voice_price'];
+ }
+ return $this['price'];
+ }
+
+ public function getPrefixPrice() {
+ return $this['mt_price'];
+ }
+
+ public function getCurrency()
+ {
+ return $this['currency'];
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ // Convert CamelCase to snake_case as that's how we use array access in every other object
+ $data = [];
+ foreach ($json as $k => $v){
+ $k = ltrim(strtolower(preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', $k)), '_');
+ $data[$k] = $v;
+ }
+ $this->data = $data;
+ }
+
+ function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->data[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new Exception('Network is read only');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new Exception('Network is read only');
+ }
+}
diff --git a/vendor/nexmo/client/src/Network/Number/Callback.php b/vendor/nexmo/client/src/Network/Number/Callback.php
new file mode 100644
index 000000000..b54fc557b
--- /dev/null
+++ b/vendor/nexmo/client/src/Network/Number/Callback.php
@@ -0,0 +1,94 @@
+ 'number_type',
+ 'Network' => 'carrier_network_code',
+ 'NetworkName' => 'carrier_network_name',
+ 'Valid' => 'valid',
+ 'Ported' => 'ported',
+ 'Reachable' => 'reachable',
+ 'Roaming' => 'roaming',
+ 'RoamingCountry' => 'roaming_country_code',
+ 'RoamingNetwork' => 'roaming_network_code',
+ );
+
+ public function getId()
+ {
+ return $this->data['request_id'];
+ }
+
+ public function getCallbackTotal()
+ {
+ return $this->data['callback_total_parts'];
+ }
+
+ public function getCallbackIndex()
+ {
+ return $this->data['callback_part'];
+ }
+
+ public function getNumber()
+ {
+ return $this->data['number'];
+ }
+
+ public function __call($name, $args)
+ {
+ $type = substr($name, 0, 3);
+ $property = substr($name, 3);
+
+ if(!isset($this->optional[$property])){
+ throw new \BadMethodCallException('property does not exist: ' . $property);
+ }
+
+ $property = $this->optional[$property];
+
+ switch($type){
+ case 'get':
+ if(isset($this->data[$property])){
+ return $this->data[$property];
+ } else {
+ return null;
+ }
+ break;
+ case 'has':
+ return isset($this->data[$property]);
+ break;
+ }
+
+ throw new \BadMethodCallException('method does not exist: ' . $name);
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Network/Number/Request.php b/vendor/nexmo/client/src/Network/Number/Request.php
new file mode 100644
index 000000000..dacfe7d1c
--- /dev/null
+++ b/vendor/nexmo/client/src/Network/Number/Request.php
@@ -0,0 +1,61 @@
+params['number'] = $number;
+ $this->params['callback'] = $callback;
+ $this->params['callback_timeout'] = $timeout;
+ $this->params['callback_method'] = $method;
+ $this->params['client_ref'] = $ref;
+
+ if(!empty($features)){
+ $this->params['features'] = implode(',', $features);
+ }
+ }
+
+ public function getURI()
+ {
+ return '/ni/json';
+ }
+
+ /**
+ * @param ResponseInterface $response
+ * @return ResponseInterface
+ */
+ public function wrapResponse(ResponseInterface $response)
+ {
+ if($response->isError()){
+ return new Error($response->getData());
+ }
+
+ return new Response($response->getData());
+ }
+
+
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Network/Number/Response.php b/vendor/nexmo/client/src/Network/Number/Response.php
new file mode 100644
index 000000000..359555697
--- /dev/null
+++ b/vendor/nexmo/client/src/Network/Number/Response.php
@@ -0,0 +1,100 @@
+expected = array_merge($this->expected, array(
+ 'request_id', 'number', 'request_price', 'remaining_balance', 'callback_total_parts'
+ ));
+
+ parent::__construct($data);
+
+ foreach($callbacks as $callback){
+ if(!($callback instanceof Callback)){
+ throw new \InvalidArgumentException('callback must be of type: Nexmo\Network\Number\Callback');
+ }
+
+ if($callback->getId() !== $this->getId()){
+ throw new \InvalidArgumentException('callback id must match request id');
+ }
+ }
+
+ $this->callbacks = $callbacks;
+ }
+
+ public function getCallbackTotal()
+ {
+ return $this->data['callback_total_parts'];
+ }
+
+ public function isComplete()
+ {
+ return count($this->callbacks) == $this->getCallbackTotal();
+ }
+
+ public function getPrice()
+ {
+ return $this->data['request_price'];
+ }
+
+ public function getBalance()
+ {
+ return $this->data['remaining_balance'];
+ }
+
+ public function getNumber()
+ {
+ return $this->data['number'];
+ }
+
+ public function getId()
+ {
+ return $this->data['request_id'];
+ }
+
+ public function getStatus()
+ {
+ return $this->data['status'];
+ }
+
+ public function __call($name, $args)
+ {
+ if(empty($this->callbacks)){
+ throw new \BadMethodCallException('can not check for response data without callback data');
+ }
+
+ foreach($this->callbacks as $callback){
+ if($last = $callback->$name()){
+ return $last;
+ }
+ }
+ return $last;
+ }
+
+ public function getCallbacks()
+ {
+ return $this->callbacks;
+ }
+
+ public static function addCallback(Response $response, Callback $callback)
+ {
+ $callbacks = $response->getCallbacks();
+ $callbacks[] = $callback;
+
+ return new static($response->getData(), $callbacks);
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Numbers/Client.php b/vendor/nexmo/client/src/Numbers/Client.php
new file mode 100644
index 000000000..98cc9dcc6
--- /dev/null
+++ b/vendor/nexmo/client/src/Numbers/Client.php
@@ -0,0 +1,290 @@
+get($id);
+ }
+
+ if($number instanceof Number){
+ $body = $number->getRequestData();
+ if(!isset($update) AND !isset($body['country'])){
+ $data = $this->get($number->getId());
+ $body['msisdn'] = $data->getId();
+ $body['country'] = $data->getCountry();
+ }
+ } else {
+ $body = $number;
+ }
+
+ if(isset($update)){
+ $body['msisdn'] = $update->getId();
+ $body['country'] = $update->getCountry();
+ }
+
+ $request = new Request(
+ $this->client->getRestUrl() . '/number/update',
+ 'POST',
+ 'php://temp',
+ [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/x-www-form-urlencoded'
+ ]
+ );
+
+ $request->getBody()->write(http_build_query($body));
+ $response = $this->client->send($request);
+
+ if('200' != $response->getStatusCode()){
+ throw $this->getException($response);
+ }
+
+ if(isset($update) AND ($number instanceof Number)){
+ return $this->get($number);
+ }
+
+ if($number instanceof Number){
+ return $this->get($number);
+ }
+
+ return $this->get($body['msisdn']);
+ }
+
+ public function get($number = null)
+ {
+ $items = $this->search($number);
+
+ // This is legacy behaviour, so we need to keep it even though
+ // it isn't technically the correct message
+ if (count($items) != 1) {
+ throw new Exception\Request('number not found', 404);
+ }
+
+ return $items[0];
+ }
+
+ /**
+ * @param null|string $number
+ * @return array []Number
+ * @deprecated Use `searchOwned` instead
+ */
+ public function search($number = null)
+ {
+ return $this->searchOwned($number);
+ }
+
+ public function searchAvailable($country, $options = [])
+ {
+ $query = [
+ 'country' => $country
+ ];
+
+ // These are all optional parameters
+ $possibleParameters = [
+ 'pattern',
+ 'search_pattern',
+ 'features',
+ 'size',
+ 'type',
+ 'index'
+ ];
+
+ foreach ($possibleParameters as $param) {
+ if (isset($options[$param])) {
+ $query[$param] = $options[$param];
+ }
+ }
+
+ $request = new Request(
+ $this->client->getRestUrl() . '/number/search?' . http_build_query($query),
+ 'GET',
+ 'php://temp'
+ );
+
+ $response = $this->client->send($request);
+
+ return $this->handleNumberSearchResult($response, null);
+ }
+
+ public function searchOwned($number = null, $options = [])
+ {
+ $query = [];
+ if ($number !== null) {
+ if($number instanceof Number){
+ $query = ['pattern' => $number->getId()];
+ } else {
+ $query = ['pattern' => $number];
+ }
+
+ }
+
+ // These are all optional parameters
+ $possibleParameters = [
+ 'search_pattern',
+ 'size',
+ 'index'
+ ];
+
+ foreach ($options as $param => $value) {
+ if (!in_array($param, $possibleParameters)) {
+ throw new Exception\Request("Unknown option: '".$param."'");
+ }
+ $query[$param] = $value;
+ }
+
+ $queryString = http_build_query($query);
+
+ $request = new Request(
+ $this->client->getRestUrl() . '/account/numbers?' . $queryString,
+ 'GET',
+ 'php://temp'
+ );
+
+ $response = $this->client->send($request);
+ return $this->handleNumberSearchResult($response, $number);
+ }
+
+ private function handleNumberSearchResult($response, $number)
+ {
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $searchResults = json_decode($response->getBody()->getContents(), true);
+ if(empty($searchResults)){
+ throw new Exception\Request('number not found', 404);
+ }
+
+ if(!isset($searchResults['count']) OR !isset($searchResults['numbers'])){
+ throw new Exception\Exception('unexpected response format');
+ }
+
+ // We're going to return a list of numbers
+ $numbers = [];
+
+ // If they provided a number initially, we'll only get one response
+ // so let's reuse the object
+ if($number instanceof Number){
+ $number->jsonUnserialize($searchResults['numbers'][0]);
+ $numbers[] = $number;
+ } else {
+ // Otherwise, we return everything that matches
+ foreach ($searchResults['numbers'] as $returnedNumber) {
+ $number = new Number();
+ $number->jsonUnserialize($returnedNumber);
+ $numbers[] = $number;
+ }
+ }
+
+ return $numbers;
+ }
+
+ public function purchase($number, $country = null) {
+ // We cheat here and fetch a number using the API so that we have the country code which is required
+ // to make a cancel request
+ if (!$number instanceof Number) {
+ if (!$country) {
+ throw new Exception\Exception("You must supply a country in addition to a number to purchase a number");
+ }
+ $number = new Number($number, $country);
+ }
+
+ $body = [
+ 'msisdn' => $number->getMsisdn(),
+ 'country' => $number->getCountry()
+ ];
+
+ $request = new Request(
+ $this->client->getRestUrl() . '/number/buy',
+ 'POST',
+ 'php://temp',
+ [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/x-www-form-urlencoded'
+ ]
+ );
+
+ $request->getBody()->write(http_build_query($body));
+ $response = $this->client->send($request);
+
+ // Sadly we can't distinguish *why* purchasing fails, just that it
+ // has failed. Here are a few of the tests I attempted and their associated
+ // error codes + body
+ //
+ // Mismatch number/country :: 420 :: method failed
+ // Already own number :: 420 :: method failed
+ // Someone else owns the number :: 420 :: method failed
+ if('200' != $response->getStatusCode()){
+ throw $this->getException($response);
+ }
+ }
+
+ public function cancel($number) {
+ // We cheat here and fetch a number using the API so that we have the country code which is required
+ // to make a cancel request
+ if (!$number instanceof Number) {
+ $number = $this->get($number);
+ }
+
+ $body = [
+ 'msisdn' => $number->getMsisdn(),
+ 'country' => $number->getCountry()
+ ];
+
+ $request = new Request(
+ $this->client->getRestUrl() . '/number/cancel',
+ 'POST',
+ 'php://temp',
+ [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/x-www-form-urlencoded'
+ ]
+ );
+
+ $request->getBody()->write(http_build_query($body));
+ $response = $this->client->send($request);
+
+ // Sadly we can't distinguish *why* purchasing fails, just that it
+ // has failed.
+ if('200' != $response->getStatusCode()){
+ throw $this->getException($response);
+ }
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($body['error-code-label'], $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($body['error-code-label'], $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+}
diff --git a/vendor/nexmo/client/src/Numbers/Number.php b/vendor/nexmo/client/src/Numbers/Number.php
new file mode 100644
index 000000000..c408d664c
--- /dev/null
+++ b/vendor/nexmo/client/src/Numbers/Number.php
@@ -0,0 +1,191 @@
+data['msisdn'] = $number;
+ $this->data['country'] = $country;
+ }
+
+ public function getId()
+ {
+ return $this->fromData('msisdn');
+ }
+
+ public function getMsisdn()
+ {
+ return $this->getId();
+ }
+
+ public function getNumber()
+ {
+ return $this->getId();
+ }
+
+ public function getCountry()
+ {
+ return $this->fromData('country');
+ }
+
+ public function getType()
+ {
+ return $this->fromData('type');
+ }
+
+ public function getCost()
+ {
+ return $this->fromData('cost');
+ }
+
+ public function hasFeature($feature)
+ {
+ if(!isset($this->data['features'])){
+ return false;
+ }
+
+ return in_array($feature, $this->data['features']);
+ }
+
+ public function getFeatures()
+ {
+ return $this->fromData('features');
+ }
+
+ public function setWebhook($type, $url)
+ {
+ if(!in_array($type, [self::WEBHOOK_MESSAGE, self::WEBHOOK_VOICE_STATUS])){
+ throw new \InvalidArgumentException("invalid webhook type `$type`");
+ }
+
+ $this->data[$type] = $url;
+ return $this;
+ }
+
+ public function getWebhook($type)
+ {
+ return $this->fromData($type);
+ }
+
+ public function hasWebhook($type)
+ {
+ return isset($this->data[$type]);
+ }
+
+ public function setVoiceDestination($endpoint, $type = null)
+ {
+ if(is_null($type)){
+ $type = $this->autoType($endpoint);
+ }
+
+ if(self::ENDPOINT_APP == $type AND !($endpoint instanceof Application)){
+ $endpoint = new Application($endpoint);
+ }
+
+ $this->data['voiceCallbackValue'] = $endpoint;
+ $this->data['voiceCallbackType'] = $type;
+
+ return $this;
+ }
+
+ protected function autoType($endpoint)
+ {
+ if($endpoint instanceof Application){
+ return self::ENDPOINT_APP;
+ }
+
+ if(false !== strpos($endpoint, '@')){
+ return self::ENDPOINT_SIP;
+ }
+
+ if(0 === strpos(strtolower($endpoint), 'http')){
+ return self::ENDPOINT_VXML;
+ }
+
+ if(preg_match('#[a-z]+#', $endpoint)){
+ return self::ENDPOINT_APP;
+ }
+
+ return self::ENDPOINT_TEL;
+ }
+
+ public function getVoiceDestination()
+ {
+ return $this->fromData('voiceCallbackValue');
+ }
+
+ public function getVoiceType()
+ {
+ if(!isset($this->data['voiceCallbackType'])){
+ return null;
+ }
+
+ return $this->data['voiceCallbackType'];
+ }
+
+ protected function fromData($name)
+ {
+ if(!isset($this->data[$name])){
+ throw new \RuntimeException("`{$name}` has not been set");
+ }
+
+ return $this->data[$name];
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ $this->data = $json;
+ }
+
+ function jsonSerialize()
+ {
+ $json = $this->data;
+ if(isset($json['voiceCallbackValue']) AND ($json['voiceCallbackValue'] instanceof Application)){
+ $json['voiceCallbackValue'] = $json['voiceCallbackValue']->getId();
+ }
+
+ return $json;
+ }
+
+ public function __toString()
+ {
+ return (string) $this->getId();
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Redact/Client.php b/vendor/nexmo/client/src/Redact/Client.php
new file mode 100644
index 000000000..9e3002940
--- /dev/null
+++ b/vendor/nexmo/client/src/Redact/Client.php
@@ -0,0 +1,78 @@
+getClient()->getApiUrl() . '/v1/redact/transaction',
+ 'POST',
+ 'php://temp',
+ [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ]
+ );
+
+ $body = ['id' => $id, 'product' => $product] + $options;
+
+ $request->getBody()->write(json_encode($body));
+ $response = $this->client->send($request);
+
+ $rawBody = $response->getBody()->getContents();
+
+ if('204' != $response->getStatusCode()){
+ throw $this->getException($response);
+ }
+
+ return null;
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $response->getBody()->rewind();
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ $msg = 'Unexpected error';
+
+ // This is an error at the load balancer, likely auth related
+ if (isset($body['error_title'])) {
+ $msg = $body['error_title'];
+ }
+
+ if (isset($body['title'])) {
+ $msg = $body['title'];
+ if (isset($body['detail'])) {
+ $msg .= ' - '.$body['detail'];
+ }
+
+ $msg .= '. See '.$body['type'];
+ }
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($msg, $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($msg, $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+
+}
diff --git a/vendor/nexmo/client/src/Response.php b/vendor/nexmo/client/src/Response.php
new file mode 100644
index 000000000..ff5acee16
--- /dev/null
+++ b/vendor/nexmo/client/src/Response.php
@@ -0,0 +1,121 @@
+data = json_decode($data, true);
+ }
+
+ public function getMessages()
+ {
+ if(!isset($this->data['messages'])){
+ return array();
+ }
+
+ return $this->data['messages'];
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Count elements of an object
+ * @link http://php.net/manual/en/countable.count.php
+ * @return int The custom count as an integer.
+ *
+ *
+ * The return value is cast to an integer.
+ */
+ public function count()
+ {
+ return $this->data['message-count'];
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Return the current element
+ * @link http://php.net/manual/en/iterator.current.php
+ * @return \Nexmo\Response\Message
+ */
+ public function current()
+ {
+ if(!isset($this->messages[$this->position])){
+ $this->messages[$this->position] = new Message($this->data['messages'][$this->position]);
+ }
+
+ return $this->messages[$this->position];
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Move forward to next element
+ * @link http://php.net/manual/en/iterator.next.php
+ * @return void Any returned value is ignored.
+ */
+ public function next()
+ {
+ $this->position++;
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Return the key of the current element
+ * @link http://php.net/manual/en/iterator.key.php
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Checks if current position is valid
+ * @link http://php.net/manual/en/iterator.valid.php
+ * @return boolean The return value will be casted to boolean and then evaluated.
+ * Returns true on success or false on failure.
+ */
+ public function valid()
+ {
+ return isset($this->data['messages'][$this->position]);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Rewind the Iterator to the first element
+ * @link http://php.net/manual/en/iterator.rewind.php
+ * @return void Any returned value is ignored.
+ */
+ public function rewind()
+ {
+ $this->position = 0;
+ }
+
+ public function toArray()
+ {
+ return $this->data;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Response/Message.php b/vendor/nexmo/client/src/Response/Message.php
new file mode 100644
index 000000000..f3088a26b
--- /dev/null
+++ b/vendor/nexmo/client/src/Response/Message.php
@@ -0,0 +1,72 @@
+data = $data;
+ }
+
+ public function getStatus()
+ {
+ return $this->checkData('status');
+ }
+
+ public function getId()
+ {
+ return $this->checkData('message-id');
+ }
+
+ public function getTo()
+ {
+ return $this->checkData('to');
+ }
+
+ public function getBalance()
+ {
+ return $this->checkData('remaining-balance');
+ }
+
+ public function getPrice()
+ {
+ return $this->checkData('message-price');
+ }
+
+ public function getNetwork()
+ {
+ return $this->checkData('network');
+ }
+
+ public function getErrorMessage()
+ {
+ if(!isset($this->data['error-text'])){
+ return '';
+ }
+
+ return $this->checkData('error-text');
+ }
+
+ protected function checkData($param)
+ {
+ if(!isset($this->data[$param])){
+ throw new \RuntimeException('tried to access ' . $param . ' but data is missing');
+ }
+
+ return $this->data[$param];
+ }
+
+ public function toArray()
+ {
+ return $this->data;
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/User/Collection.php b/vendor/nexmo/client/src/User/Collection.php
new file mode 100644
index 000000000..00dba8d91
--- /dev/null
+++ b/vendor/nexmo/client/src/User/Collection.php
@@ -0,0 +1,194 @@
+setClient($this->getClient());
+ $idOrUser->jsonUnserialize($data);
+
+ return $idOrUser;
+ }
+
+ public function hydrateAll($users)
+ {
+ $hydrated = [];
+ foreach ($users as $u) {
+ $key = isset($u['user_id']) ? 'user_id' : 'id';
+ $user = new User($u[$key]);
+
+ // Setting the client makes us run out of memory and I'm not sure why yet
+ // $idOrUser->setClient($this->getClient());
+
+ $user->jsonUnserialize($u);
+ $hydrated[] = $user;
+ }
+
+ return $hydrated;
+ }
+
+ /**
+ * @param null $user
+ * @return $this|User
+ */
+ public function __invoke(Filter $filter = null)
+ {
+ if(!is_null($filter)){
+ $this->setFilter($filter);
+ }
+
+ return $this;
+ }
+
+ public function fetch() {
+ $this->fetchPage(self::getCollectionPath());
+ return $this->hydrateAll($this->page);
+ }
+
+ public function create($user)
+ {
+ return $this->post($user);
+ }
+
+ public function post($user)
+ {
+ if($user instanceof User){
+ $body = $user->getRequestData();
+ } else {
+ $body = $user;
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $this->getCollectionPath()
+ ,'POST',
+ 'php://temp',
+ ['content-type' => 'application/json']
+ );
+
+ $request->getBody()->write(json_encode($body));
+ $response = $this->client->send($request);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $body = json_decode($response->getBody()->getContents(), true);
+ $user = new User($body['id']);
+ $user->jsonUnserialize($body);
+ $user->setClient($this->getClient());
+
+ return $user;
+ }
+
+ public function get($user)
+ {
+ if(!($user instanceof User)){
+ $user = new User($user);
+ }
+
+ $user->setClient($this->getClient());
+ $user->get();
+
+ return $user;
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ // This message isn't very useful, but we shouldn't ever see it
+ $errorTitle = 'Unexpected error';
+
+ if (isset($body['code'])) {
+ $errorTitle = $body['code'];
+ }
+
+ if (isset($body['description']) && $body['description']) {
+ $errorTitle = $body['description'];
+ }
+
+ if (isset($body['error_title'])) {
+ $errorTitle = $body['error_title'];
+ }
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($errorTitle, $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($errorTitle, $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+ public function offsetExists($offset)
+ {
+ return true;
+ }
+
+ /**
+ * @param mixed $user
+ * @return User
+ */
+ public function offsetGet($user)
+ {
+ if(!($user instanceof User)){
+ $user = new User($user);
+ }
+
+ $user->setClient($this->getClient());
+ return $user;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ throw new \RuntimeException('can not set collection properties');
+ }
+
+ public function offsetUnset($offset)
+ {
+ throw new \RuntimeException('can not unset collection properties');
+ }
+}
diff --git a/vendor/nexmo/client/src/User/User.php b/vendor/nexmo/client/src/User/User.php
new file mode 100644
index 000000000..928bd922e
--- /dev/null
+++ b/vendor/nexmo/client/src/User/User.php
@@ -0,0 +1,138 @@
+data['id'] = $id;
+ }
+
+ public function setName($name)
+ {
+ $this->data['name'] = $name;
+ return $this;
+ }
+
+ public function getId()
+ {
+ return $this->data['id'];
+ }
+
+ public function __toString()
+ {
+ return (string)$this->getId();
+ }
+
+
+ public function get()
+ {
+ $request = new Request(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath() . '/' . $this->getId()
+ ,'GET'
+ );
+
+ $response = $this->getClient()->send($request);
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $data = json_decode($response->getBody()->getContents(), true);
+ $this->jsonUnserialize($data);
+
+ return $this;
+ }
+
+ public function getConversations() {
+ $response = $this->getClient()->get(
+ $this->getClient()->getApiUrl() . Collection::getCollectionPath().'/'.$this->getId().'/conversations'
+ );
+
+ if($response->getStatusCode() != '200'){
+ throw $this->getException($response);
+ }
+
+ $data = json_decode($response->getBody()->getContents(), true);
+ $conversationCollection = $this->getClient()->conversation();
+
+ return $conversationCollection->hydrateAll($data);
+ }
+
+ public function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function jsonUnserialize(array $json)
+ {
+ $this->data = $json;
+ }
+
+ public function getRequestDataForConversation()
+ {
+ return [
+ 'user_id' => $this->getId()
+ ];
+ }
+
+ protected function getException(ResponseInterface $response)
+ {
+ $body = json_decode($response->getBody()->getContents(), true);
+ $status = $response->getStatusCode();
+
+ // This message isn't very useful, but we shouldn't ever see it
+ $errorTitle = 'Unexpected error';
+
+ if (isset($body['code'])) {
+ $errorTitle = $body['code'];
+ }
+
+ if (isset($body['description']) && $body['description']) {
+ $errorTitle = $body['description'];
+ }
+
+ if (isset($body['error_title'])) {
+ $errorTitle = $body['error_title'];
+ }
+
+ if($status >= 400 AND $status < 500) {
+ $e = new Exception\Request($errorTitle, $status);
+ } elseif($status >= 500 AND $status < 600) {
+ $e = new Exception\Server($errorTitle, $status);
+ } else {
+ $e = new Exception\Exception('Unexpected HTTP Status Code');
+ throw $e;
+ }
+
+ return $e;
+ }
+
+
+}
diff --git a/vendor/nexmo/client/src/Verify/Check.php b/vendor/nexmo/client/src/Verify/Check.php
new file mode 100644
index 000000000..1857b0922
--- /dev/null
+++ b/vendor/nexmo/client/src/Verify/Check.php
@@ -0,0 +1,48 @@
+data = $data;
+ }
+
+ public function getCode()
+ {
+ return $this->data['code'];
+ }
+
+ public function getDate()
+ {
+ return new \DateTime($this->data['date_received']);
+ }
+
+ public function getStatus()
+ {
+ return $this->data['status'];
+ }
+
+ public function getIpAddress()
+ {
+ return $this->data['ip_address'];
+ }
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Verify/Client.php b/vendor/nexmo/client/src/Verify/Client.php
new file mode 100644
index 000000000..5bf68eb9a
--- /dev/null
+++ b/vendor/nexmo/client/src/Verify/Client.php
@@ -0,0 +1,231 @@
+createVerificationFromArray($verification);
+ }
+
+ $params = $verification->getRequestData(false);
+
+ $request = $this->getRequest($params);
+ $response = $this->client->send($request);
+
+ $data = $this->processReqRes($verification, $request, $response, true);
+ return $this->checkError($verification, $data);
+ }
+
+ public function search($verification)
+ {
+ if(!($verification instanceof Verification)){
+ $verification = new Verification($verification);
+ }
+
+ $params = [
+ 'request_id' => $verification->getRequestId()
+ ];
+
+ $request = $this->getRequest($params, 'search');
+ $response = $this->client->send($request);
+
+ $data = $this->processReqRes($verification, $request, $response, true);
+
+ if(!isset($data['status'])){
+ throw new Exception\Exception('unexpected response from API');
+ }
+
+ //verify API returns text status on success
+ if(!is_numeric($data['status'])){
+ return $verification;
+ }
+
+ //normalize errors (client vrs server)
+ switch($data['status']){
+ case '5':
+ $e = new Exception\Server($data['error_text'], $data['status']);
+ break;
+ default:
+ $e = new Exception\Request($data['error_text'], $data['status']);
+ break;
+ }
+
+ $e->setEntity($verification);
+ throw $e;
+ }
+
+ public function cancel($verification)
+ {
+ return $this->control($verification, 'cancel');
+ }
+
+ public function trigger($verification)
+ {
+ return $this->control($verification, 'trigger_next_event');
+ }
+
+ public function check($verification, $code, $ip = null)
+ {
+ if(!($verification instanceof Verification)){
+ $verification = new Verification($verification);
+ }
+
+ $params = [
+ 'request_id' => $verification->getRequestId(),
+ 'code' => $code
+ ];
+
+ if(!is_null($ip)){
+ $params['ip'] = $ip;
+ }
+
+ $request = $this->getRequest($params, 'check');
+ $response = $this->client->send($request);
+
+ $data = $this->processReqRes($verification, $request, $response, false);
+ return $this->checkError($verification, $data);
+ }
+
+ public function serialize(Verification $verification)
+ {
+ return serialize($verification);
+ }
+
+ public function unserialize($verification)
+ {
+ if(is_string($verification)){
+ $verification = unserialize($verification);
+ }
+
+ if(!($verification instanceof Verification)){
+ throw new \InvalidArgumentException('expected verification object or serialize verification object');
+ }
+
+ $verification->setClient($this);
+ return $verification;
+ }
+
+ protected function control($verification, $cmd)
+ {
+ if(!($verification instanceof Verification)){
+ $verification = new Verification($verification);
+ }
+
+ $params = [
+ 'request_id' => $verification->getRequestId(),
+ 'cmd' => $cmd
+ ];
+
+ $request = $this->getRequest($params, 'control');
+ $response = $this->client->send($request);
+
+ $data = $this->processReqRes($verification, $request, $response, false);
+ return $this->checkError($verification, $data);
+ }
+
+ protected function checkError(Verification $verification, $data)
+ {
+ if(!isset($data['status'])){
+ throw new Exception\Exception('unexpected response from API');
+ }
+
+ //normalize errors (client vrs server)
+ switch($data['status']){
+ case '0':
+ return $verification;
+ case '5':
+ $e = new Exception\Server($data['error_text'], $data['status']);
+ break;
+ default:
+ $e = new Exception\Request($data['error_text'], $data['status']);
+ break;
+ }
+
+ $e->setEntity($verification);
+ throw $e;
+ }
+
+ protected function processReqRes(Verification $verification, RequestInterface $req, ResponseInterface $res, $replace = true)
+ {
+ $verification->setClient($this);
+
+ if($replace || !$verification->getRequest()){
+ $verification->setRequest($req);
+ }
+
+ if($replace || !$verification->getResponse()) {
+ $verification->setResponse($res);
+ return $verification->getResponseData();
+ }
+
+ if($res->getBody()->isSeekable()){
+ $res->getBody()->rewind();
+ }
+
+ return json_decode($res->getBody()->getContents(), true);
+ }
+
+ protected function getRequest($params, $path = null)
+ {
+ if(!is_null($path)){
+ $path = '/verify/' . $path . '/json';
+ } else {
+ $path = '/verify/json';
+ }
+
+ $request = new Request(
+ $this->getClient()->getApiUrl() . $path,
+ 'POST',
+ 'php://temp',
+ [
+ 'content-type' => 'application/json'
+ ]
+ );
+
+ $request->getBody()->write(json_encode($params));
+ return $request;
+ }
+
+ /**
+ * @param $array
+ * @return Verification
+ */
+ protected function createVerificationFromArray($array)
+ {
+ if(!is_array($array)){
+ throw new \RuntimeException('verification must implement `' . VerificationInterface::class . '` or be an array`');
+ }
+
+ foreach(['number', 'brand'] as $param){
+ if(!isset($array[$param])){
+ throw new \InvalidArgumentException('missing expected key `' . $param . '`');
+ }
+ }
+
+ $number = $array['number'];
+ $brand = $array['brand'];
+
+ unset($array['number']);
+ unset($array['brand']);
+
+ return new Verification($number, $brand, $array);
+ }
+}
diff --git a/vendor/nexmo/client/src/Verify/Verification.php b/vendor/nexmo/client/src/Verify/Verification.php
new file mode 100644
index 000000000..9fee6ab96
--- /dev/null
+++ b/vendor/nexmo/client/src/Verify/Verification.php
@@ -0,0 +1,596 @@
+dirty = false;
+ $this->requestData['request_id'] = $idOrNumber;
+ } else {
+ $this->dirty = true;
+ $this->requestData['number'] = $idOrNumber;
+ $this->requestData['brand'] = $brand;
+ $this->requestData = array_merge($this->requestData, $additional);
+ }
+ }
+
+ /**
+ * Allow Verification to have actions.
+ *
+ * @param Client $client Verify Client
+ * @return $this
+ */
+ public function setClient(Client $client)
+ {
+ $this->client = $client;
+ return $this;
+ }
+
+ /**
+ * @return Client
+ */
+ protected function useClient()
+ {
+ if(isset($this->client)){
+ return $this->client;
+ }
+
+ throw new \RuntimeException('can not act on the verification directly unless a verify client has been set');
+ }
+
+ /**
+ * Check if the code is correct. Unlike the method it proxies, an invalid code does not throw an exception.
+ *
+ * @uses \Nexmo\Verify\Client::check()
+ * @param string $code Numeric code provided by the user.
+ * @param null|string $ip IP address to be used for the verification.
+ * @return bool Code is valid.
+ * @throws RequestException
+ */
+ public function check($code, $ip = null)
+ {
+ try {
+ $this->useClient()->check($this, $code, $ip);
+ return true;
+ } catch(RequestException $e) {
+ if($e->getCode() == 16 || $e->getCode() == 17){
+ return false;
+ }
+
+ throw $e;
+ }
+ }
+
+ /**
+ * Cancel the verification.
+ *
+ * @uses \Nexmo\Verify\Client::cancel()
+ */
+ public function cancel()
+ {
+ $this->useClient()->cancel($this);
+ }
+
+ /**
+ * Trigger the next verification.
+ *
+ * @uses \Nexmo\Verify\Client::trigger()
+ */
+ public function trigger()
+ {
+ $this->useClient()->trigger($this);
+ }
+
+ /**
+ * Update Verification from the API.
+ *
+ * @uses \Nexmo\Verify\Client::search()
+ */
+ public function sync()
+ {
+ $this->useClient()->search($this);
+ }
+
+ /**
+ * Check if the user provided data has sent to the API yet.
+ *
+ * @return bool
+ */
+ public function isDirty()
+ {
+ return $this->dirty;
+ }
+
+ /**
+ * If do not set number in international format or you are not sure if number is correctly formatted, set country
+ * with the two-character country code. For example, GB, US. Verify works out the international phone number for
+ * you.
+ * @link https://docs.nexmo.com/verify/api-reference/api-reference#vrequest
+ *
+ * Can only be set before the verification is created.
+ * @uses \Nexmo\Entity\RequestArrayTrait::setRequestData
+ *
+ * @param $country
+ * @return $this
+ * @throws \Exception
+ */
+ public function setCountry($country)
+ {
+ return $this->setRequestData('country', $country);
+ }
+
+ /**
+ * An 11 character alphanumeric string to specify the SenderID for SMS sent by Verify. Depending on the destination
+ * of the phone number you are applying, restrictions may apply. By default, sender_id is VERIFY.
+ * @link https://docs.nexmo.com/verify/api-reference/api-reference#vrequest
+ *
+ * Can only be set before the verification is created.
+ * @uses \Nexmo\Entity\RequestArrayTrait::setRequestData
+ *
+ * @param $id
+ * @return $this
+ * @throws \Exception
+ */
+ public function setSenderId($id)
+ {
+ return $this->setRequestData('sender_id', $id);
+ }
+
+ /**
+ * The length of the PIN. Possible values are 6 or 4 characters. The default value is 4.
+ * @link https://docs.nexmo.com/verify/api-reference/api-reference#vrequest
+ *
+ * Can only be set before the verification is created.
+ * @uses \Nexmo\Entity\RequestArrayTrait::setRequestData
+ *
+ * @param $length
+ * @return $this
+ * @throws \Exception
+ */
+ public function setCodeLength($length)
+ {
+ return $this->setRequestData('code_length', $length);
+ }
+
+ /**
+ * By default, TTS are generated in the locale that matches number. For example, the TTS for a 33* number is sent in
+ * French. Use this parameter to explicitly control the language, accent and gender used for the Verify request. The
+ * default language is en-us.
+ * @link https://docs.nexmo.com/verify/api-reference/api-reference#vrequest
+ *
+ * Can only be set before the verification is created.
+ * @uses \Nexmo\Entity\RequestArrayTrait::setRequestData
+ *
+ * @param $language
+ * @return $this
+ * @throws \Exception
+ */
+ public function setLanguage($language)
+ {
+ return $this->setRequestData('lg', $language);
+ }
+
+ /**
+ * Restrict verification to a certain network type. Possible values are:
+ * - All (Default)
+ * - Mobile
+ * - Landline
+ *
+ * Note: contact support@nexmo.com to enable this feature.
+ * @link https://docs.nexmo.com/verify/api-reference/api-reference#vrequest
+ *
+ * Can only be set before the verification is created.
+ * @uses \Nexmo\Entity\RequestArrayTrait::setRequestData
+ *
+ * @param $type
+ * @return $this
+ * @throws \Exception
+ */
+ public function setRequireType($type)
+ {
+ return $this->setRequestData('require_type', $type);
+ }
+
+ /**
+ * The PIN validity time from generation. This is an integer value between 30 and 3600 seconds. The default is 300
+ * seconds. When specified together, pin_expiry must be an integer multiple of next_event_wait. Otherwise,
+ * pin_expiry is set to next_event_wait.
+ * @link https://docs.nexmo.com/verify/api-reference/api-reference#vrequest
+ *
+ * Can only be set before the verification is created.
+ * @uses \Nexmo\Entity\RequestArrayTrait::setRequestData
+ *
+ * @param $time
+ * @return $this
+ * @throws \Exception
+ */
+ public function setPinExpiry($time)
+ {
+ return $this->setRequestData('pin_expiry', $time);
+ }
+
+ /**
+ * An integer value between 60 and 900 seconds inclusive that specifies the wait time between attempts to deliver
+ * the PIN. Verify calculates the default value based on the average time taken by users to complete verification.
+ * @link https://docs.nexmo.com/verify/api-reference/api-reference#vrequest
+ *
+ * Can only be set before the verification is created.
+ * @uses \Nexmo\Entity\RequestArrayTrait::setRequestData
+ *
+ * @param $time
+ * @return $this
+ * @throws \Exception
+ */
+ public function setWaitTime($time)
+ {
+ return $this->setRequestData('next_event_wait', $time);
+ }
+
+ /**
+ * Get the verification request id, if available.
+ *
+ * @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @return string|null
+ */
+ public function getRequestId()
+ {
+ return $this->proxyArrayAccess('request_id');
+ }
+
+ /**
+ * Get the number verified / to be verified.
+ *
+ * @see \Nexmo\Verify\Verification::__construct()
+ * @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @return string|null
+ */
+ public function getNumber()
+ {
+ return $this->proxyArrayAccess('number');
+ }
+
+ /**
+ * Get the account id, if available.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @return string|null
+ */
+ public function getAccountId()
+ {
+ return $this->proxyArrayAccess('account_id');
+ }
+
+ /**
+ * Get the sender id, if available.
+ *
+ * @see \Nexmo\Verify\Verification::setSenderId();
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @return string|null
+ */
+ public function getSenderId()
+ {
+ return $this->proxyArrayAccess('sender_id');
+ }
+
+ /**
+ * Get the price of the verification, if available.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @return string|null
+ */
+ public function getPrice()
+ {
+ return $this->proxyArrayAccess('price');
+ }
+
+ /**
+ * Get the currency used to price the verification, if available.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @return string|null
+ */
+ public function getCurrency()
+ {
+ return $this->proxyArrayAccess('currency');
+ }
+
+ /**
+ * Get the status of the verification, if available.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @return string|null
+ */
+ public function getStatus()
+ {
+ return $this->proxyArrayAccess('status');
+ }
+
+ /**
+ * Get an array of verification checks, if available. Will return an empty array if no check have been made, or if
+ * the data is not available.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @return \Nexmo\Verify\Check[]|\Nexmo\Verify\Check
+ */
+ public function getChecks()
+ {
+ $checks = $this->proxyArrayAccess('checks');
+ if(!$checks){
+ return [];
+ }
+
+ foreach($checks as $i => $check) {
+ $checks[$i] = new Check($check);
+ }
+
+ return $checks;
+ }
+
+ /**
+ * Get the date the verification started.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccessDate()
+ *
+ * @return \DateTime|null
+ */
+ public function getSubmitted()
+ {
+ return $this->proxyArrayAccessDate('date_submitted');
+ }
+
+ /**
+ * Get the date the verification stopped.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccessDate()
+ *
+ * @return \DateTime|null
+ */
+ public function getFinalized()
+ {
+ return $this->proxyArrayAccessDate('date_finalized');
+ }
+
+ /**
+ * Get the date of the first verification event.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccessDate()
+ *
+ * @return \DateTime|null
+ */
+ public function getFirstEvent()
+ {
+ return $this->proxyArrayAccessDate('first_event_date');
+ }
+
+ /**
+ * Get the date of the last verification event.
+ *
+ * Only available after a searching for a verification.
+ * @see \Nexmo\Verify\Client::search();
+ *
+ * However still @uses \Nexmo\Verify\Verification::proxyArrayAccessDate()
+ *
+ * @return \DateTime|null
+ */
+ public function getLastEvent()
+ {
+ return $this->proxyArrayAccessDate('last_event_date');
+ }
+
+ /**
+ * Proxies `proxyArrayAccess()` and returns a DateTime if the parameter is found.
+ * @uses \Nexmo\Verify\Verification::proxyArrayAccess()
+ *
+ * @param string $param Parameter to look for.
+ * @return \DateTime
+ */
+ protected function proxyArrayAccessDate($param)
+ {
+ $date = $this->proxyArrayAccess($param);
+ if($date) {
+ return new \DateTime($date);
+ }
+ }
+
+ /**
+ * Simply proxies array access to check for a parameter in the response, request, or user provided data.
+ *
+ * @uses \Nexmo\Verify\Verification::offsetGet();
+ * @uses \Nexmo\Verify\Verification::offsetExists();
+ *
+ * @param string $param Parameter to look for.
+ * @return mixed
+ */
+ protected function proxyArrayAccess($param)
+ {
+ if(isset($this[$param])){
+ return $this[$param];
+ }
+ }
+
+ /**
+ * Allow the object to access the data from the API response, a sent API request, or the user set data that the
+ * request will be created from - in that order.
+ *
+ * @param mixed $offset
+ * @return bool
+ * @throws \Exception
+ */
+ public function offsetExists($offset)
+ {
+ $response = $this->getResponseData();
+ $request = $this->getRequestData();
+ $dirty = $this->requestData;
+ return isset($response[$offset]) || isset($request[$offset]) || isset($dirty[$offset]);
+ }
+
+ /**
+ * Allow the object to access the data from the API response, a sent API request, or the user set data that the
+ * request will be created from - in that order.
+ *
+ * @param mixed $offset
+ * @return mixed
+ * @throws \Exception
+ */
+ public function offsetGet($offset)
+ {
+ $response = $this->getResponseData();
+ $request = $this->getRequestData();
+ $dirty = $this->requestData;
+
+ if(isset($response[$offset])){
+ return $response[$offset];
+ }
+
+ if(isset($request[$offset])){
+ return $request[$offset];
+ }
+
+ if(isset($dirty[$offset])){
+ return $dirty[$offset];
+ }
+ }
+
+ /**
+ * All properties are read only.
+ *
+ * @param mixed $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw $this->getReadOnlyException($offset);
+ }
+
+ /**
+ * All properties are read only.
+ *
+ * @param mixed $offset
+ */
+ public function offsetUnset($offset)
+ {
+ throw $this->getReadOnlyException($offset);
+ }
+
+ /**
+ * All properties are read only.
+ *
+ * @param $offset
+ * @return \RuntimeException
+ */
+ protected function getReadOnlyException($offset)
+ {
+ return new \RuntimeException(sprintf(
+ 'can not modify `%s` using array access',
+ $offset
+ ));
+ }
+
+ public function serialize()
+ {
+ $data = [
+ 'requestData' => $this->requestData
+ ];
+
+ if($request = $this->getRequest()){
+ $data['request'] = \Zend\Diactoros\Request\Serializer::toString($request);
+ }
+
+ if($response = $this->getResponse()){
+ $data['response'] = \Zend\Diactoros\Response\Serializer::toString($response);
+ }
+
+ return serialize($data);
+ }
+
+ public function unserialize($serialized)
+ {
+ $data = unserialize($serialized);
+
+ $this->requestData = $data['requestData'];
+
+ if(isset($data['request'])){
+ $this->request = \Zend\Diactoros\Request\Serializer::fromString($data['request']);
+ }
+
+ if(isset($data['response'])){
+ $this->response = \Zend\Diactoros\Response\Serializer::fromString($data['response']);
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Verify/VerificationInterface.php b/vendor/nexmo/client/src/Verify/VerificationInterface.php
new file mode 100644
index 000000000..cad2e8faa
--- /dev/null
+++ b/vendor/nexmo/client/src/Verify/VerificationInterface.php
@@ -0,0 +1,22 @@
+params['answer_url'] = $url;
+ $this->params['to'] = $to;
+
+ if(!is_null($from)){
+ $this->params['from'] = $from;
+ }
+ }
+
+ public function setAnswer($url, $method = null)
+ {
+ $this->params['answer_url'] = $url;
+ if(!is_null($method)){
+ $this->params['answer_method'] = $method;
+ } else {
+ unset($this->params['answer_method']);
+ }
+
+ return $this;
+ }
+
+ public function setError($url, $method = null)
+ {
+ $this->params['error_url'] = $url;
+ if(!is_null($method)){
+ $this->params['error_method'] = $method;
+ } else {
+ unset($this->params['error_method']);
+ }
+
+ return $this;
+ }
+
+ public function setStatus($url, $method = null)
+ {
+ $this->params['status_url'] = $url;
+ if(!is_null($method)){
+ $this->params['status_method'] = $method;
+ } else {
+ unset($this->params['status_method']);
+ }
+
+ return $this;
+ }
+
+
+ public function setMachineDetection($hangup = true, $timeout = null)
+ {
+ $this->params['machine_detection'] = ($hangup ? 'hangup' : 'true');
+ if(!is_null($timeout)){
+ $this->params['machine_timeout'] = (int) $timeout;
+ } else {
+ unset($this->params['machine_timeout']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getURI()
+ {
+ return '/call/json';
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Voice/Call/Inbound.php b/vendor/nexmo/client/src/Voice/Call/Inbound.php
new file mode 100644
index 000000000..2166d004c
--- /dev/null
+++ b/vendor/nexmo/client/src/Voice/Call/Inbound.php
@@ -0,0 +1,15 @@
+data['call-id'];
+ }
+
+ public function getTo()
+ {
+ return $this->data['to'];
+ }
+
+ public function getStatus()
+ {
+ return $this->data['status'];
+ }
+
+ public function getPrice()
+ {
+ return $this->data['call-price'];
+ }
+
+ public function getRate()
+ {
+ return $this->data['call-rate'];
+ }
+
+ public function getDuration()
+ {
+ return $this->data['call-duration'];
+ }
+
+ public function getCreated()
+ {
+ return \DateTime::createFromFormat(self::TIME_FORMAT, $this->data['call-request']);
+ }
+
+ public function getStart()
+ {
+ if(!isset($this->data['call-start'])){
+ return null;
+ }
+
+ return \DateTime::createFromFormat(self::TIME_FORMAT, $this->data['call-start']);
+ }
+
+ public function getEnd()
+ {
+ if(!isset($this->data['call-end'])){
+ return null;
+ }
+
+ return \DateTime::createFromFormat(self::TIME_FORMAT, $this->data['call-end']);
+ }
+
+ public function getNetwork()
+ {
+ return $this->data['network-code'];
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/nexmo/client/src/Voice/Message/Message.php b/vendor/nexmo/client/src/Voice/Message/Message.php
new file mode 100644
index 000000000..3986944d2
--- /dev/null
+++ b/vendor/nexmo/client/src/Voice/Message/Message.php
@@ -0,0 +1,73 @@
+params['text'] = $text;
+ $this->params['to'] = $to;
+ $this->params['from'] = $from;
+ }
+
+ public function setLanguage($lang)
+ {
+ $this->params['lg'] = $lang;
+ return $this;
+ }
+
+ public function setVoice($voice)
+ {
+ $this->params['voice'] = $voice;
+ return $this;
+ }
+
+ public function setRepeat($count)
+ {
+ $this->params['repeat'] = (int) $count;
+ return $this;
+ }
+ public function setCallback($url, $method = null)
+ {
+ $this->params['callback'] = $url;
+ if(!is_null($method)){
+ $this->params['callback_method'] = $method;
+ } else {
+ unset($this->params['callback_method']);
+ }
+
+ return $this;
+ }
+
+ public function setMachineDetection($hangup = true, $timeout = null)
+ {
+ $this->params['machine_detection'] = ($hangup ? 'hangup' : 'true');
+ if(!is_null($timeout)){
+ $this->params['machine_timeout'] = (int) $timeout;
+ } else {
+ unset($this->params['machine_timeout']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getURI()
+ {
+ return '/tts/json';
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/nikic/php-parser/.travis.yml b/vendor/nikic/php-parser/.travis.yml
index 989d7a049..ee8eba99c 100644
--- a/vendor/nikic/php-parser/.travis.yml
+++ b/vendor/nikic/php-parser/.travis.yml
@@ -23,7 +23,7 @@ matrix:
script:
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then vendor/bin/phpunit --coverage-clover build/logs/clover.xml; else vendor/bin/phpunit; fi
- - if [ $TRAVIS_PHP_VERSION = '7.1' ]; then test_old/run-php-src.sh; fi
+ - if [ $TRAVIS_PHP_VERSION = '7.2' ]; then test_old/run-php-src.sh; fi
after_success:
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then php vendor/bin/coveralls; fi
diff --git a/vendor/nikic/php-parser/CHANGELOG.md b/vendor/nikic/php-parser/CHANGELOG.md
index 70d85e557..4a3f0063f 100644
--- a/vendor/nikic/php-parser/CHANGELOG.md
+++ b/vendor/nikic/php-parser/CHANGELOG.md
@@ -1,8 +1,25 @@
-Version 4.0.5-dev
+Version 4.1.1-dev
-----------------
Nothing yet.
+Version 4.1.0 (2018-10-10)
+--------------------------
+
+### Added
+
+* Added support for PHP 7.3 flexible heredoc/nowdoc strings, completing support for PHP 7.3. There
+ are two caveats for this feature:
+ * In some rare, pathological cases flexible heredoc/nowdoc strings change the interpretation of
+ existing doc strings. PHP-Parser will now use the new interpretation.
+ * Flexible heredoc/nowdoc strings require special support from the lexer. Because this is not
+ available on PHP versions before 7.3, support has to be emulated. This emulation is not perfect
+ and some cases which we do not expect to occur in practice (such as flexible doc strings being
+ nested within each other through abuse of variable-variable interpolation syntax) may not be
+ recognized correctly.
+* Added `DONT_TRAVERSER_CURRENT_AND_CHILDREN` to `NodeTraverser` to skip both traversal of child
+ nodes, and prevent subsequent visitors from visiting the current node.
+
Version 4.0.4 (2018-09-18)
--------------------------
diff --git a/vendor/nikic/php-parser/README.md b/vendor/nikic/php-parser/README.md
index 5fea5c906..5ae789f9a 100644
--- a/vendor/nikic/php-parser/README.md
+++ b/vendor/nikic/php-parser/README.md
@@ -6,7 +6,7 @@ PHP Parser
This is a PHP 5.2 to PHP 7.2 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
-[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.2).
+[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.3).
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
diff --git a/vendor/nikic/php-parser/composer.json b/vendor/nikic/php-parser/composer.json
index 7ae425ff7..12e8458a4 100644
--- a/vendor/nikic/php-parser/composer.json
+++ b/vendor/nikic/php-parser/composer.json
@@ -24,7 +24,7 @@
"bin": ["bin/php-parse"],
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "4.1-dev"
}
}
}
diff --git a/vendor/nikic/php-parser/grammar/php5.y b/vendor/nikic/php-parser/grammar/php5.y
index 486d836c8..abc5cedfa 100644
--- a/vendor/nikic/php-parser/grammar/php5.y
+++ b/vendor/nikic/php-parser/grammar/php5.y
@@ -791,11 +791,9 @@ common_scalar:
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
- { $attrs = attributes(); setDocStringAttrs($attrs, $1);
- $$ = new Scalar\String_(Scalar\String_::parseDocString($1, $2, false), $attrs); }
+ { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), false); }
| T_START_HEREDOC T_END_HEREDOC
- { $attrs = attributes(); setDocStringAttrs($attrs, $1);
- $$ = new Scalar\String_('', $attrs); }
+ { $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), false); }
;
static_scalar:
@@ -856,8 +854,7 @@ scalar:
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
| T_START_HEREDOC encaps_list T_END_HEREDOC
- { $attrs = attributes(); setDocStringAttrs($attrs, $1);
- parseEncapsedDoc($2, true); $$ = new Scalar\Encapsed($2, $attrs); }
+ { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
;
static_array_pair_list:
diff --git a/vendor/nikic/php-parser/grammar/php7.y b/vendor/nikic/php-parser/grammar/php7.y
index ba203284b..f7d32ef4b 100644
--- a/vendor/nikic/php-parser/grammar/php7.y
+++ b/vendor/nikic/php-parser/grammar/php7.y
@@ -847,17 +847,14 @@ scalar:
| dereferencable_scalar { $$ = $1; }
| constant { $$ = $1; }
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
- { $attrs = attributes(); setDocStringAttrs($attrs, $1);
- $$ = new Scalar\String_(Scalar\String_::parseDocString($1, $2), $attrs); }
+ { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
| T_START_HEREDOC T_END_HEREDOC
- { $attrs = attributes(); setDocStringAttrs($attrs, $1);
- $$ = new Scalar\String_('', $attrs); }
+ { $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), true); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
| T_START_HEREDOC encaps_list T_END_HEREDOC
- { $attrs = attributes(); setDocStringAttrs($attrs, $1);
- parseEncapsedDoc($2, true); $$ = new Scalar\Encapsed($2, $attrs); }
+ { $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
;
optional_expr:
diff --git a/vendor/nikic/php-parser/grammar/rebuildParsers.php b/vendor/nikic/php-parser/grammar/rebuildParsers.php
index 3be5edb64..1882d5fe5 100644
--- a/vendor/nikic/php-parser/grammar/rebuildParsers.php
+++ b/vendor/nikic/php-parser/grammar/rebuildParsers.php
@@ -166,15 +166,6 @@ function resolveMacros($code) {
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
}
- if ('parseEncapsedDoc' == $name) {
- assertArgs(2, $args, $name);
-
- return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
- . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, ' . $args[1] . '); } }'
- . ' $s->value = preg_replace(\'~(\r\n|\n|\r)\z~\', \'\', $s->value);'
- . ' if (\'\' === $s->value) array_pop(' . $args[0] . ');';
- }
-
if ('makeNop' == $name) {
assertArgs(3, $args, $name);
@@ -192,15 +183,6 @@ function resolveMacros($code) {
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
}
- if ('setDocStringAttrs' == $name) {
- assertArgs(2, $args, $name);
-
- return $args[0] . '[\'kind\'] = strpos(' . $args[1] . ', "\'") === false '
- . '? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; '
- . 'preg_match(\'/\A[bB]?<<<[ \t]*[\\\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\\\'"]?(?:\r\n|\n|\r)\z/\', ' . $args[1] . ', $matches); '
- . $args[0] . '[\'docLabel\'] = $matches[1];';
- }
-
if ('prependLeadingComments' == $name) {
assertArgs(1, $args, $name);
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php
index 647aaa34c..b62d79cc4 100644
--- a/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php
+++ b/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php
@@ -2,7 +2,202 @@
namespace PhpParser\Lexer;
+use PhpParser\Error;
+use PhpParser\ErrorHandler;
+
class Emulative extends \PhpParser\Lexer
{
- /* No features requiring emulation have been added in PHP > 7.0 */
-}
+ const PHP_7_3 = '7.3.0dev';
+
+ /**
+ * @var array Patches used to reverse changes introduced in the code
+ */
+ private $patches;
+
+ public function startLexing(string $code, ErrorHandler $errorHandler = null) {
+ $this->patches = [];
+ $preparedCode = $this->prepareCode($code);
+ if (null === $preparedCode) {
+ // Nothing to emulate, yay
+ parent::startLexing($code, $errorHandler);
+ return;
+ }
+
+ $collector = new ErrorHandler\Collecting();
+ parent::startLexing($preparedCode, $collector);
+ $this->fixupTokens();
+
+ $errors = $collector->getErrors();
+ if (!empty($errors)) {
+ $this->fixupErrors($errors);
+ foreach ($errors as $error) {
+ $errorHandler->handleError($error);
+ }
+ }
+ }
+
+ /**
+ * Prepares code for emulation. If nothing has to be emulated null is returned.
+ *
+ * @param string $code
+ * @return null|string
+ */
+ private function prepareCode(string $code) {
+ if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) {
+ return null;
+ }
+
+ if (strpos($code, '<<<') === false) {
+ // Definitely doesn't contain heredoc/nowdoc
+ return null;
+ }
+
+ $flexibleDocStringRegex = <<<'REGEX'
+/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
+(?:.*\r?\n)*?
+(?\h*)\2(?![a-zA-Z_\x80-\xff])(?(?:;?[\r\n])?)/x
+REGEX;
+ if (!preg_match_all($flexibleDocStringRegex, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
+ // No heredoc/nowdoc found
+ return null;
+ }
+
+ // Keep track of how much we need to adjust string offsets due to the modifications we
+ // already made
+ $posDelta = 0;
+ foreach ($matches as $match) {
+ $indentation = $match['indentation'][0];
+ $indentationStart = $match['indentation'][1];
+
+ $separator = $match['separator'][0];
+ $separatorStart = $match['separator'][1];
+
+ if ($indentation === '' && $separator !== '') {
+ // Ordinary heredoc/nowdoc
+ continue;
+ }
+
+ if ($indentation !== '') {
+ // Remove indentation
+ $indentationLen = strlen($indentation);
+ $code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
+ $this->patches[] = [$indentationStart + $posDelta, 'add', $indentation];
+ $posDelta -= $indentationLen;
+ }
+
+ if ($separator === '') {
+ // Insert newline as separator
+ $code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
+ $this->patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
+ $posDelta += 1;
+ }
+ }
+
+ if (empty($this->patches)) {
+ // We did not end up emulating anything
+ return null;
+ }
+
+ return $code;
+ }
+
+ private function fixupTokens() {
+ assert(count($this->patches) > 0);
+
+ // Load first patch
+ $patchIdx = 0;
+ list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
+
+ // We use a manual loop over the tokens, because we modify the array on the fly
+ $pos = 0;
+ for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
+ $token = $this->tokens[$i];
+ if (\is_string($token)) {
+ // We assume that patches don't apply to string tokens
+ $pos += \strlen($token);
+ continue;
+ }
+
+ $len = \strlen($token[1]);
+ $posDelta = 0;
+ while ($patchPos >= $pos && $patchPos < $pos + $len) {
+ $patchTextLen = \strlen($patchText);
+ if ($patchType === 'remove') {
+ if ($patchPos === $pos && $patchTextLen === $len) {
+ // Remove token entirely
+ array_splice($this->tokens, $i, 1, []);
+ $i--;
+ $c--;
+ } else {
+ // Remove from token string
+ $this->tokens[$i][1] = substr_replace(
+ $token[1], '', $patchPos - $pos + $posDelta, $patchTextLen
+ );
+ $posDelta -= $patchTextLen;
+ }
+ } elseif ($patchType === 'add') {
+ // Insert into the token string
+ $this->tokens[$i][1] = substr_replace(
+ $token[1], $patchText, $patchPos - $pos + $posDelta, 0
+ );
+ $posDelta += $patchTextLen;
+ } else {
+ assert(false);
+ }
+
+ // Fetch the next patch
+ $patchIdx++;
+ if ($patchIdx >= \count($this->patches)) {
+ // No more patches, we're done
+ return;
+ }
+
+ list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
+
+ // Multiple patches may apply to the same token. Reload the current one to check
+ // If the new patch applies
+ $token = $this->tokens[$i];
+ }
+
+ $pos += $len;
+ }
+
+ // A patch did not apply
+ assert(false);
+ }
+
+ /**
+ * Fixup line and position information in errors.
+ *
+ * @param Error[] $errors
+ */
+ private function fixupErrors(array $errors) {
+ foreach ($errors as $error) {
+ $attrs = $error->getAttributes();
+
+ $posDelta = 0;
+ $lineDelta = 0;
+ foreach ($this->patches as $patch) {
+ list($patchPos, $patchType, $patchText) = $patch;
+ if ($patchPos >= $attrs['startFilePos']) {
+ // No longer relevant
+ break;
+ }
+
+ if ($patchType === 'add') {
+ $posDelta += strlen($patchText);
+ $lineDelta += substr_count($patchText, "\n");
+ } else {
+ $posDelta -= strlen($patchText);
+ $lineDelta -= substr_count($patchText, "\n");
+ }
+ }
+
+ $attrs['startFilePos'] += $posDelta;
+ $attrs['endFilePos'] += $posDelta;
+ $attrs['startLine'] += $lineDelta;
+ $attrs['endLine'] += $lineDelta;
+ $error->setAttributes($attrs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php
index 5dc2d3161..e467c2219 100644
--- a/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php
+++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php
@@ -10,7 +10,7 @@ class StaticCall extends Expr
{
/** @var Node\Name|Expr Class name */
public $class;
- /** @var string|Identifier|Expr Method name */
+ /** @var Identifier|Expr Method name */
public $name;
/** @var Node\Arg[] Arguments */
public $args;
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php
index 93a8633de..cf12e8c6d 100644
--- a/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php
+++ b/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php
@@ -134,29 +134,6 @@ class String_ extends Scalar
}
throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
}
-
- /**
- * @internal
- *
- * Parses a constant doc string.
- *
- * @param string $startToken Doc string start token content (<<visitors as $visitor) {
+ $breakVisitorIndex = null;
+
+ foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
@@ -116,6 +127,10 @@ class NodeTraverser implements NodeTraverserInterface
$subNode = $return;
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
+ } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
+ $traverseChildren = false;
+ $breakVisitorIndex = $visitorIndex;
+ break;
} elseif (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
@@ -134,8 +149,9 @@ class NodeTraverser implements NodeTraverserInterface
}
}
- foreach ($this->visitors as $visitor) {
+ foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->leaveNode($subNode);
+
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
@@ -154,6 +170,10 @@ class NodeTraverser implements NodeTraverserInterface
);
}
}
+
+ if ($breakVisitorIndex === $visitorIndex) {
+ break;
+ }
}
}
}
@@ -174,7 +194,9 @@ class NodeTraverser implements NodeTraverserInterface
foreach ($nodes as $i => &$node) {
if ($node instanceof Node) {
$traverseChildren = true;
- foreach ($this->visitors as $visitor) {
+ $breakVisitorIndex = null;
+
+ foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
@@ -182,6 +204,10 @@ class NodeTraverser implements NodeTraverserInterface
$node = $return;
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
+ } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
+ $traverseChildren = false;
+ $breakVisitorIndex = $visitorIndex;
+ break;
} elseif (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
@@ -200,8 +226,9 @@ class NodeTraverser implements NodeTraverserInterface
}
}
- foreach ($this->visitors as $visitor) {
+ foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->leaveNode($node);
+
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
@@ -226,6 +253,10 @@ class NodeTraverser implements NodeTraverserInterface
);
}
}
+
+ if ($breakVisitorIndex === $visitorIndex) {
+ break;
+ }
}
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php
index b5ebb3273..4da2dc979 100644
--- a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php
+++ b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php
@@ -2264,12 +2264,10 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
436 => function ($stackPos) {
- $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
- $this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], false), $attrs);
+ $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], false);
},
437 => function ($stackPos) {
- $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(2-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(2-1)], $matches); $attrs['docLabel'] = $matches[1];;
- $this->semValue = new Scalar\String_('', $attrs);
+ $this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], false);
},
438 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
@@ -2405,8 +2403,7 @@ class Php5 extends \PhpParser\ParserAbstract
foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
},
482 => function ($stackPos) {
- $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
- foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, true); } } $s->value = preg_replace('~(\r\n|\n|\r)\z~', '', $s->value); if ('' === $s->value) array_pop($this->semStack[$stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
+ $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
483 => function ($stackPos) {
$this->semValue = array();
diff --git a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php
index 24ce4d14b..768da7217 100644
--- a/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php
+++ b/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php
@@ -2201,20 +2201,17 @@ class Php7 extends \PhpParser\ParserAbstract
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
445 => function ($stackPos) {
- $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
- $this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)]), $attrs);
+ $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
446 => function ($stackPos) {
- $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(2-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(2-1)], $matches); $attrs['docLabel'] = $matches[1];;
- $this->semValue = new Scalar\String_('', $attrs);
+ $this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], true);
},
447 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
},
448 => function ($stackPos) {
- $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
- foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, true); } } $s->value = preg_replace('~(\r\n|\n|\r)\z~', '', $s->value); if ('' === $s->value) array_pop($this->semStack[$stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
+ $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
449 => function ($stackPos) {
$this->semValue = null;
diff --git a/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php b/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php
index dab6aea7a..8b7bba83f 100644
--- a/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php
+++ b/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php
@@ -9,6 +9,7 @@ namespace PhpParser;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
+use PhpParser\Node\Scalar\Encapsed;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
@@ -710,6 +711,119 @@ abstract class ParserAbstract implements Parser
return new LNumber($num, $attributes);
}
+ protected function stripIndentation(
+ string $string, int $indentLen, string $indentChar,
+ bool $newlineAtStart, bool $newlineAtEnd, array $attributes
+ ) {
+ if ($indentLen === 0) {
+ return $string;
+ }
+
+ $start = $newlineAtStart ? '(?:(?<=\n)|\A)' : '(?<=\n)';
+ $end = $newlineAtEnd ? '(?:(?=[\r\n])|\z)' : '(?=[\r\n])';
+ $regex = '/' . $start . '([ \t]*)(' . $end . ')?/';
+ return preg_replace_callback(
+ $regex,
+ function ($matches) use ($indentLen, $indentChar, $attributes) {
+ $prefix = substr($matches[1], 0, $indentLen);
+ if (false !== strpos($prefix, $indentChar === " " ? "\t" : " ")) {
+ $this->emitError(new Error(
+ 'Invalid indentation - tabs and spaces cannot be mixed', $attributes
+ ));
+ } elseif (strlen($prefix) < $indentLen && !isset($matches[2])) {
+ $this->emitError(new Error(
+ 'Invalid body indentation level ' .
+ '(expecting an indentation level of at least ' . $indentLen . ')',
+ $attributes
+ ));
+ }
+ return substr($matches[0], strlen($prefix));
+ },
+ $string
+ );
+ }
+
+ protected function parseDocString(
+ string $startToken, $contents, string $endToken,
+ array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape
+ ) {
+ $kind = strpos($startToken, "'") === false
+ ? String_::KIND_HEREDOC : String_::KIND_NOWDOC;
+
+ $regex = '/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/';
+ $result = preg_match($regex, $startToken, $matches);
+ assert($result === 1);
+ $label = $matches[1];
+
+ $result = preg_match('/\A[ \t]*/', $endToken, $matches);
+ assert($result === 1);
+ $indentation = $matches[0];
+
+ $attributes['kind'] = $kind;
+ $attributes['docLabel'] = $label;
+ $attributes['docIndentation'] = $indentation;
+
+ $indentHasSpaces = false !== strpos($indentation, " ");
+ $indentHasTabs = false !== strpos($indentation, "\t");
+ if ($indentHasSpaces && $indentHasTabs) {
+ $this->emitError(new Error(
+ 'Invalid indentation - tabs and spaces cannot be mixed',
+ $endTokenAttributes
+ ));
+
+ // Proceed processing as if this doc string is not indented
+ $indentation = '';
+ }
+
+ $indentLen = \strlen($indentation);
+ $indentChar = $indentHasSpaces ? " " : "\t";
+
+ if (\is_string($contents)) {
+ if ($contents === '') {
+ return new String_('', $attributes);
+ }
+
+ $contents = $this->stripIndentation(
+ $contents, $indentLen, $indentChar, true, true, $attributes
+ );
+ $contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents);
+
+ if ($kind === String_::KIND_HEREDOC) {
+ $contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape);
+ }
+
+ return new String_($contents, $attributes);
+ } else {
+ assert(count($contents) > 0);
+ if (!$contents[0] instanceof Node\Scalar\EncapsedStringPart) {
+ // If there is no leading encapsed string part, pretend there is an empty one
+ $this->stripIndentation(
+ '', $indentLen, $indentChar, true, false, $contents[0]->getAttributes()
+ );
+ }
+
+ $newContents = [];
+ foreach ($contents as $i => $part) {
+ if ($part instanceof Node\Scalar\EncapsedStringPart) {
+ $isLast = $i === \count($contents) - 1;
+ $part->value = $this->stripIndentation(
+ $part->value, $indentLen, $indentChar,
+ $i === 0, $isLast, $part->getAttributes()
+ );
+ $part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape);
+ if ($isLast) {
+ $part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value);
+ }
+ if ('' === $part->value) {
+ continue;
+ }
+ }
+ $newContents[] = $part;
+ }
+ return new Encapsed($newContents, $attributes);
+ }
+ }
+
protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere
try {
diff --git a/vendor/nikic/php-parser/test/PhpParser/Builder/ClassTest.php b/vendor/nikic/php-parser/test/PhpParser/Builder/ClassTest.php
index fe324d766..27d911b0e 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Builder/ClassTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Builder/ClassTest.php
@@ -123,39 +123,31 @@ DOC;
);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Unexpected node of type "Stmt_Echo"
- */
public function testInvalidStmtError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
$this->createClassBuilder('Test')
->addStmt(new Stmt\Echo_([]))
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Doc comment must be a string or an instance of PhpParser\Comment\Doc
- */
public function testInvalidDocComment() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
$this->createClassBuilder('Test')
->setDocComment(new Comment('Test'));
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Name cannot be empty
- */
public function testEmptyName() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Name cannot be empty');
$this->createClassBuilder('Test')
->extend('');
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Name must be a string or an instance of Node\Name
- */
public function testInvalidName() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
$this->createClassBuilder('Test')
->extend(['Foo']);
}
diff --git a/vendor/nikic/php-parser/test/PhpParser/Builder/FunctionTest.php b/vendor/nikic/php-parser/test/PhpParser/Builder/FunctionTest.php
index a2c621922..b7413e1fd 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Builder/FunctionTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Builder/FunctionTest.php
@@ -92,29 +92,23 @@ class FunctionTest extends TestCase
], []), $node);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage void type cannot be nullable
- */
public function testInvalidNullableVoidType() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('void type cannot be nullable');
$this->createFunctionBuilder('test')->setReturnType('?void');
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Expected parameter node, got "Name"
- */
public function testInvalidParamError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Expected parameter node, got "Name"');
$this->createFunctionBuilder('test')
->addParam(new Node\Name('foo'))
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Expected statement or expression node
- */
public function testAddNonStmt() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Expected statement or expression node');
$this->createFunctionBuilder('test')
->addStmt(new Node\Name('Test'));
}
diff --git a/vendor/nikic/php-parser/test/PhpParser/Builder/InterfaceTest.php b/vendor/nikic/php-parser/test/PhpParser/Builder/InterfaceTest.php
index 2c926b605..3ea3533c7 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Builder/InterfaceTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Builder/InterfaceTest.php
@@ -79,11 +79,9 @@ class InterfaceTest extends TestCase
]), $node);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Unexpected node of type "Stmt_PropertyProperty"
- */
public function testInvalidStmtError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Unexpected node of type "Stmt_PropertyProperty"');
$this->builder->addStmt(new Stmt\PropertyProperty('invalid'));
}
diff --git a/vendor/nikic/php-parser/test/PhpParser/Builder/MethodTest.php b/vendor/nikic/php-parser/test/PhpParser/Builder/MethodTest.php
index 0decbed0d..141967903 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Builder/MethodTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Builder/MethodTest.php
@@ -135,33 +135,27 @@ class MethodTest extends TestCase
], []), $node);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot add statements to an abstract method
- */
public function testAddStmtToAbstractMethodError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Cannot add statements to an abstract method');
$this->createMethodBuilder('test')
->makeAbstract()
->addStmt(new Print_(new String_('test')))
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot make method with statements abstract
- */
public function testMakeMethodWithStmtsAbstractError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Cannot make method with statements abstract');
$this->createMethodBuilder('test')
->addStmt(new Print_(new String_('test')))
->makeAbstract()
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Expected parameter node, got "Name"
- */
public function testInvalidParamError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Expected parameter node, got "Name"');
$this->createMethodBuilder('test')
->addParam(new Node\Name('foo'))
;
diff --git a/vendor/nikic/php-parser/test/PhpParser/Builder/ParamTest.php b/vendor/nikic/php-parser/test/PhpParser/Builder/ParamTest.php
index e0606259c..9ed283b7a 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Builder/ParamTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Builder/ParamTest.php
@@ -129,19 +129,15 @@ class ParamTest extends TestCase
];
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Parameter type cannot be void
- */
public function testVoidTypeError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Parameter type cannot be void');
$this->createParamBuilder('test')->setType('void');
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Type must be a string, or an instance of Name, Identifier or NullableType
- */
public function testInvalidTypeError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or NullableType');
$this->createParamBuilder('test')->setType(new \stdClass);
}
diff --git a/vendor/nikic/php-parser/test/PhpParser/Builder/TraitTest.php b/vendor/nikic/php-parser/test/PhpParser/Builder/TraitTest.php
index 666769dc3..31535b2c3 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Builder/TraitTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Builder/TraitTest.php
@@ -37,11 +37,9 @@ class TraitTest extends TestCase
]), $trait);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Unexpected node of type "Stmt_Echo"
- */
public function testInvalidStmtError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
$this->createTraitBuilder('Test')
->addStmt(new Stmt\Echo_([]))
;
diff --git a/vendor/nikic/php-parser/test/PhpParser/Builder/TraitUseAdaptationTest.php b/vendor/nikic/php-parser/test/PhpParser/Builder/TraitUseAdaptationTest.php
index 06f99dc35..18f00b9d8 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Builder/TraitUseAdaptationTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Builder/TraitUseAdaptationTest.php
@@ -55,65 +55,53 @@ class TraitUseAdaptationTest extends TestCase
);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot set alias for not alias adaptation buider
- */
public function testAsOnNotAlias() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Cannot set alias for not alias adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo')
->insteadof('AnotherTrait')
->as('bar')
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot add overwritten traits for not precedence adaptation buider
- */
public function testInsteadofOnNotPrecedence() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Cannot add overwritten traits for not precedence adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo')
->as('bar')
->insteadof('AnotherTrait')
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Precedence adaptation must have trait
- */
public function testInsteadofWithoutTrait() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Precedence adaptation must have trait');
$this->createTraitUseAdaptationBuilder(null, 'foo')
->insteadof('AnotherTrait')
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot set access modifier for not alias adaptation buider
- */
public function testMakeOnNotAlias() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Cannot set access modifier for not alias adaptation buider');
$this->createTraitUseAdaptationBuilder('Test', 'foo')
->insteadof('AnotherTrait')
->makePublic()
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Multiple access type modifiers are not allowed
- */
public function testMultipleMake() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Multiple access type modifiers are not allowed');
$this->createTraitUseAdaptationBuilder(null, 'foo')
->makePrivate()
->makePublic()
;
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Type of adaptation is not defined
- */
public function testUndefinedType() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Type of adaptation is not defined');
$this->createTraitUseAdaptationBuilder(null, 'foo')
->getNode()
;
diff --git a/vendor/nikic/php-parser/test/PhpParser/Builder/TraitUseTest.php b/vendor/nikic/php-parser/test/PhpParser/Builder/TraitUseTest.php
index fe5b3ee1b..ec4e9400a 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Builder/TraitUseTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Builder/TraitUseTest.php
@@ -45,11 +45,9 @@ class TraitUseTest extends TestCase
);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Adaptation must have type TraitUseAdaptation
- */
public function testInvalidAdaptationNode() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Adaptation must have type TraitUseAdaptation');
$this->createTraitUseBuilder('Test')
->with(new Stmt\Echo_([]))
;
diff --git a/vendor/nikic/php-parser/test/PhpParser/BuilderFactoryTest.php b/vendor/nikic/php-parser/test/PhpParser/BuilderFactoryTest.php
index 08b0736d0..9dd20a109 100644
--- a/vendor/nikic/php-parser/test/PhpParser/BuilderFactoryTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/BuilderFactoryTest.php
@@ -69,19 +69,15 @@ class BuilderFactoryTest extends TestCase
);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Expected at least two expressions
- */
public function testConcatOneError() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Expected at least two expressions');
(new BuilderFactory())->concat("a");
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Expected string or Expr
- */
public function testConcatInvalidExpr() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Expected string or Expr');
(new BuilderFactory())->concat("a", 42);
}
@@ -218,35 +214,27 @@ class BuilderFactoryTest extends TestCase
);
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Expected string or instance of Node\Identifier
- */
public function testInvalidIdentifier() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Expected string or instance of Node\Identifier');
(new BuilderFactory())->classConstFetch('Foo', new Expr\Variable('foo'));
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Expected string or instance of Node\Identifier or Node\Expr
- */
public function testInvalidIdentifierOrExpr() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Expected string or instance of Node\Identifier or Node\Expr');
(new BuilderFactory())->staticCall('Foo', new Name('bar'));
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Name must be a string or an instance of Node\Name or Node\Expr
- */
public function testInvalidNameOrExpr() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
(new BuilderFactory())->funcCall(new Node\Stmt\Return_());
}
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Variable name must be string or Expr
- */
public function testInvalidVar() {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Variable name must be string or Expr');
(new BuilderFactory())->var(new Node\Stmt\Return_());
}
diff --git a/vendor/nikic/php-parser/test/PhpParser/ConstExprEvaluatorTest.php b/vendor/nikic/php-parser/test/PhpParser/ConstExprEvaluatorTest.php
index d0a83fff9..7cd56b5b5 100644
--- a/vendor/nikic/php-parser/test/PhpParser/ConstExprEvaluatorTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/ConstExprEvaluatorTest.php
@@ -73,11 +73,9 @@ class ConstExprEvaluatorTest extends TestCase
];
}
- /**
- * @expectedException \PhpParser\ConstExprEvaluationException
- * @expectedExceptionMessage Expression of type Expr_Variable cannot be evaluated
- */
public function testEvaluateFails() {
+ $this->expectException(ConstExprEvaluationException::class);
+ $this->expectExceptionMessage('Expression of type Expr_Variable cannot be evaluated');
$evaluator = new ConstExprEvaluator();
$evaluator->evaluateDirectly(new Expr\Variable('a'));
}
diff --git a/vendor/nikic/php-parser/test/PhpParser/ErrorHandler/ThrowingTest.php b/vendor/nikic/php-parser/test/PhpParser/ErrorHandler/ThrowingTest.php
index 31d349ae4..8735be8cc 100644
--- a/vendor/nikic/php-parser/test/PhpParser/ErrorHandler/ThrowingTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/ErrorHandler/ThrowingTest.php
@@ -7,11 +7,9 @@ use PHPUnit\Framework\TestCase;
class ThrowingTest extends TestCase
{
- /**
- * @expectedException \PhpParser\Error
- * @expectedExceptionMessage Test
- */
public function testHandleError() {
+ $this->expectException(Error::class);
+ $this->expectExceptionMessage('Test');
$errorHandler = new Throwing();
$errorHandler->handleError(new Error('Test'));
}
diff --git a/vendor/nikic/php-parser/test/PhpParser/ErrorTest.php b/vendor/nikic/php-parser/test/PhpParser/ErrorTest.php
index 4b9d73a16..07a3288f5 100644
--- a/vendor/nikic/php-parser/test/PhpParser/ErrorTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/ErrorTest.php
@@ -94,11 +94,9 @@ class ErrorTest extends TestCase
}
}
- /**
- * @expectedException \RuntimeException
- * @expectedExceptionMessage Invalid position information
- */
public function testInvalidPosInfo() {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Invalid position information');
$error = new Error('Some error', [
'startFilePos' => 10,
'endFilePos' => 11,
diff --git a/vendor/nikic/php-parser/test/PhpParser/Lexer/EmulativeTest.php b/vendor/nikic/php-parser/test/PhpParser/Lexer/EmulativeTest.php
index 3a33ba820..533547ae4 100644
--- a/vendor/nikic/php-parser/test/PhpParser/Lexer/EmulativeTest.php
+++ b/vendor/nikic/php-parser/test/PhpParser/Lexer/EmulativeTest.php
@@ -2,6 +2,7 @@
namespace PhpParser\Lexer;
+use PhpParser\ErrorHandler;
use PhpParser\LexerTest;
use PhpParser\Parser\Tokens;
@@ -63,12 +64,11 @@ class EmulativeTest extends LexerTest
$lexer = $this->getLexer();
$lexer->startLexing('assertSame($expectedTokenType, $lexer->getNextToken($text));
- $this->assertSame($expectedTokenText, $text);
+ $tokens = [];
+ while (0 !== $token = $lexer->getNextToken($text)) {
+ $tokens[] = [$token, $text];
}
- $this->assertSame(0, $lexer->getNextToken());
+ $this->assertSame($expectedTokens, $tokens);
}
/**
@@ -85,6 +85,29 @@ class EmulativeTest extends LexerTest
$this->assertSame(0, $lexer->getNextToken());
}
+ /**
+ * @dataProvider provideTestLexNewFeatures
+ */
+ public function testErrorAfterEmulation($code) {
+ $errorHandler = new ErrorHandler\Collecting;
+ $lexer = $this->getLexer([]);
+ $lexer->startLexing('getErrors();
+ $this->assertCount(1, $errors);
+
+ $error = $errors[0];
+ $this->assertSame('Unexpected null byte', $error->getRawMessage());
+
+ $attrs = $error->getAttributes();
+ $expPos = strlen('assertSame($expPos, $attrs['startFilePos']);
+ $this->assertSame($expPos, $attrs['endFilePos']);
+ $this->assertSame($expLine, $attrs['startLine']);
+ $this->assertSame($expLine, $attrs['endLine']);
+ }
+
public function provideTestLexNewFeatures() {
return [
['yield from', [
@@ -128,6 +151,43 @@ class EmulativeTest extends LexerTest
[Tokens::T_END_HEREDOC, 'NOWDOC'],
[ord(';'), ';'],
]],
+
+ // Flexible heredoc/nowdoc
+ ["<<