+ */
+class MercurialProcessor implements ProcessorInterface
+{
+ private $level;
+ private static $cache;
+
+ public function __construct($level = Logger::DEBUG)
+ {
+ $this->level = Logger::toMonologLevel($level);
+ }
+
+ /**
+ * @param array $record
+ * @return array
+ */
+ public function __invoke(array $record)
+ {
+ // return if the level is not high enough
+ if ($record['level'] < $this->level) {
+ return $record;
+ }
+
+ $record['extra']['hg'] = self::getMercurialInfo();
+
+ return $record;
+ }
+
+ private static function getMercurialInfo()
+ {
+ if (self::$cache) {
+ return self::$cache;
+ }
+
+ $result = explode(' ', trim(`hg id -nb`));
+ if (count($result) >= 3) {
+ return self::$cache = array(
+ 'branch' => $result[1],
+ 'revision' => $result[2],
+ );
+ }
+
+ return self::$cache = array();
+ }
+}
diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php
new file mode 100644
index 0000000..66b80fb
--- /dev/null
+++ b/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.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\Processor;
+
+/**
+ * Adds value of getmypid into records
+ *
+ * @author Andreas Hörnicke
+ */
+class ProcessIdProcessor implements ProcessorInterface
+{
+ /**
+ * @param array $record
+ * @return array
+ */
+ public function __invoke(array $record)
+ {
+ $record['extra']['process_id'] = getmypid();
+
+ return $record;
+ }
+}
diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/core/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php
new file mode 100644
index 0000000..7e64d4d
--- /dev/null
+++ b/core/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/core/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php
new file mode 100644
index 0000000..0088505
--- /dev/null
+++ b/core/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\Utils;
+
+/**
+ * Processes a record's message according to PSR-3 rules
+ *
+ * It replaces {foo} with the value from $context['foo']
+ *
+ * @author Jordi Boggiano
+ */
+class PsrLogMessageProcessor implements ProcessorInterface
+{
+ /**
+ * @param array $record
+ * @return array
+ */
+ public function __invoke(array $record)
+ {
+ if (false === strpos($record['message'], '{')) {
+ return $record;
+ }
+
+ $replacements = array();
+ foreach ($record['context'] as $key => $val) {
+ if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) {
+ $replacements['{'.$key.'}'] = $val;
+ } elseif (is_object($val)) {
+ $replacements['{'.$key.'}'] = '[object '.Utils::getClass($val).']';
+ } else {
+ $replacements['{'.$key.'}'] = '['.gettype($val).']';
+ }
+ }
+
+ $record['message'] = strtr($record['message'], $replacements);
+
+ return $record;
+ }
+}
diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php
new file mode 100644
index 0000000..615a4d9
--- /dev/null
+++ b/core/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+/**
+ * Adds a tags array into record
+ *
+ * @author Martijn Riemers
+ */
+class TagProcessor implements ProcessorInterface
+{
+ private $tags;
+
+ public function __construct(array $tags = array())
+ {
+ $this->setTags($tags);
+ }
+
+ public function addTags(array $tags = array())
+ {
+ $this->tags = array_merge($this->tags, $tags);
+ }
+
+ public function setTags(array $tags = array())
+ {
+ $this->tags = $tags;
+ }
+
+ public function __invoke(array $record)
+ {
+ $record['extra']['tags'] = $this->tags;
+
+ return $record;
+ }
+}
diff --git a/core/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php
new file mode 100644
index 0000000..d1f708c
--- /dev/null
+++ b/core/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\ResettableInterface;
+
+/**
+ * Adds a unique identifier into records
+ *
+ * @author Simon Mönch
+ */
+class UidProcessor implements ProcessorInterface, ResettableInterface
+{
+ private $uid;
+
+ public function __construct($length = 7)
+ {
+ if (!is_int($length) || $length > 32 || $length < 1) {
+ throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32');
+ }
+
+
+ $this->uid = $this->generateUid($length);
+ }
+
+ public function __invoke(array $record)
+ {
+ $record['extra']['uid'] = $this->uid;
+
+ return $record;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUid()
+ {
+ 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/core/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/core/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php
new file mode 100644
index 0000000..684188f
--- /dev/null
+++ b/core/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+/**
+ * Injects url/method and remote IP of the current web request in all records
+ *
+ * @author Jordi Boggiano
+ */
+class WebProcessor implements ProcessorInterface
+{
+ /**
+ * @var array|\ArrayAccess
+ */
+ protected $serverData;
+
+ /**
+ * Default fields
+ *
+ * Array is structured as [key in record.extra => key in $serverData]
+ *
+ * @var array
+ */
+ protected $extraFields = array(
+ 'url' => 'REQUEST_URI',
+ 'ip' => 'REMOTE_ADDR',
+ 'http_method' => 'REQUEST_METHOD',
+ 'server' => 'SERVER_NAME',
+ 'referrer' => 'HTTP_REFERER',
+ );
+
+ /**
+ * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data
+ * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer
+ */
+ public function __construct($serverData = null, array $extraFields = null)
+ {
+ if (null === $serverData) {
+ $this->serverData = &$_SERVER;
+ } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) {
+ $this->serverData = $serverData;
+ } else {
+ throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.');
+ }
+
+ if (null !== $extraFields) {
+ if (isset($extraFields[0])) {
+ foreach (array_keys($this->extraFields) as $fieldName) {
+ if (!in_array($fieldName, $extraFields)) {
+ unset($this->extraFields[$fieldName]);
+ }
+ }
+ } else {
+ $this->extraFields = $extraFields;
+ }
+ }
+ }
+
+ /**
+ * @param array $record
+ * @return array
+ */
+ public function __invoke(array $record)
+ {
+ // skip processing if for some reason request data
+ // is not present (CLI or wonky SAPIs)
+ if (!isset($this->serverData['REQUEST_URI'])) {
+ return $record;
+ }
+
+ $record['extra'] = $this->appendExtraFields($record['extra']);
+
+ return $record;
+ }
+
+ /**
+ * @param string $extraName
+ * @param string $serverName
+ * @return $this
+ */
+ public function addExtraField($extraName, $serverName)
+ {
+ $this->extraFields[$extraName] = $serverName;
+
+ return $this;
+ }
+
+ /**
+ * @param array $extra
+ * @return array
+ */
+ private function appendExtraFields(array $extra)
+ {
+ foreach ($this->extraFields as $extraName => $serverName) {
+ $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null;
+ }
+
+ if (isset($this->serverData['UNIQUE_ID'])) {
+ $extra['unique_id'] = $this->serverData['UNIQUE_ID'];
+ }
+
+ return $extra;
+ }
+}
diff --git a/core/vendor/monolog/monolog/src/Monolog/Registry.php b/core/vendor/monolog/monolog/src/Monolog/Registry.php
new file mode 100644
index 0000000..159b751
--- /dev/null
+++ b/core/vendor/monolog/monolog/src/Monolog/Registry.php
@@ -0,0 +1,134 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+use InvalidArgumentException;
+
+/**
+ * Monolog log registry
+ *
+ * Allows to get `Logger` instances in the global scope
+ * via static method calls on this class.
+ *
+ *
+ * $application = new Monolog\Logger('application');
+ * $api = new Monolog\Logger('api');
+ *
+ * Monolog\Registry::addLogger($application);
+ * Monolog\Registry::addLogger($api);
+ *
+ * function testLogger()
+ * {
+ * Monolog\Registry::api()->addError('Sent to $api Logger instance');
+ * Monolog\Registry::application()->addError('Sent to $application Logger instance');
+ * }
+ *
+ *
+ * @author Tomas Tatarko
+ */
+class Registry
+{
+ /**
+ * List of all loggers in the registry (by named indexes)
+ *
+ * @var Logger[]
+ */
+ private static $loggers = array();
+
+ /**
+ * Adds new logging channel to the registry
+ *
+ * @param Logger $logger Instance of the logging channel
+ * @param string|null $name Name of the logging channel ($logger->getName() by default)
+ * @param bool $overwrite Overwrite instance in the registry if the given name already exists?
+ * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists
+ */
+ public static function addLogger(Logger $logger, $name = null, $overwrite = false)
+ {
+ $name = $name ?: $logger->getName();
+
+ if (isset(self::$loggers[$name]) && !$overwrite) {
+ throw new InvalidArgumentException('Logger with the given name already exists');
+ }
+
+ self::$loggers[$name] = $logger;
+ }
+
+ /**
+ * Checks if such logging channel exists by name or instance
+ *
+ * @param string|Logger $logger Name or logger instance
+ */
+ public static function hasLogger($logger)
+ {
+ if ($logger instanceof Logger) {
+ $index = array_search($logger, self::$loggers, true);
+
+ return false !== $index;
+ } else {
+ return isset(self::$loggers[$logger]);
+ }
+ }
+
+ /**
+ * Removes instance from registry by name or instance
+ *
+ * @param string|Logger $logger Name or logger instance
+ */
+ public static function removeLogger($logger)
+ {
+ if ($logger instanceof Logger) {
+ if (false !== ($idx = array_search($logger, self::$loggers, true))) {
+ unset(self::$loggers[$idx]);
+ }
+ } else {
+ unset(self::$loggers[$logger]);
+ }
+ }
+
+ /**
+ * Clears the registry
+ */
+ public static function clear()
+ {
+ self::$loggers = array();
+ }
+
+ /**
+ * Gets Logger instance from the registry
+ *
+ * @param string $name Name of the requested Logger instance
+ * @throws \InvalidArgumentException If named Logger instance is not in the registry
+ * @return Logger Requested instance of Logger
+ */
+ public static function getInstance($name)
+ {
+ if (!isset(self::$loggers[$name])) {
+ throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name));
+ }
+
+ return self::$loggers[$name];
+ }
+
+ /**
+ * Gets Logger instance from the registry via static method call
+ *
+ * @param string $name Name of the requested Logger instance
+ * @param array $arguments Arguments passed to static method call
+ * @throws \InvalidArgumentException If named Logger instance is not in the registry
+ * @return Logger Requested instance of Logger
+ */
+ public static function __callStatic($name, $arguments)
+ {
+ return self::getInstance($name);
+ }
+}
diff --git a/core/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/core/vendor/monolog/monolog/src/Monolog/ResettableInterface.php
new file mode 100644
index 0000000..635bc77
--- /dev/null
+++ b/core/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/core/vendor/monolog/monolog/src/Monolog/SignalHandler.php b/core/vendor/monolog/monolog/src/Monolog/SignalHandler.php
new file mode 100644
index 0000000..d590780
--- /dev/null
+++ b/core/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/core/vendor/monolog/monolog/src/Monolog/Utils.php b/core/vendor/monolog/monolog/src/Monolog/Utils.php
new file mode 100644
index 0000000..eb9be86
--- /dev/null
+++ b/core/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/core/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php
new file mode 100644
index 0000000..a9a3f30
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.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;
+
+use Monolog\Handler\TestHandler;
+
+class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
+{
+ public function testHandleError()
+ {
+ $logger = new Logger('test', array($handler = new TestHandler));
+ $errHandler = new ErrorHandler($logger);
+
+ $errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false);
+ trigger_error('Foo', E_USER_ERROR);
+ $this->assertCount(1, $handler->getRecords());
+ $this->assertTrue($handler->hasErrorRecords());
+ trigger_error('Foo', E_USER_NOTICE);
+ $this->assertCount(2, $handler->getRecords());
+ $this->assertTrue($handler->hasEmergencyRecords());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php
new file mode 100644
index 0000000..71c4204
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php
@@ -0,0 +1,158 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+
+class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Monolog\Formatter\ChromePHPFormatter::format
+ */
+ public function testDefaultFormat()
+ {
+ $formatter = new ChromePHPFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('ip' => '127.0.0.1'),
+ 'message' => 'log',
+ );
+
+ $message = $formatter->format($record);
+
+ $this->assertEquals(
+ array(
+ 'meh',
+ array(
+ 'message' => 'log',
+ 'context' => array('from' => 'logger'),
+ 'extra' => array('ip' => '127.0.0.1'),
+ ),
+ 'unknown',
+ 'error',
+ ),
+ $message
+ );
+ }
+
+ /**
+ * @covers Monolog\Formatter\ChromePHPFormatter::format
+ */
+ public function testFormatWithFileAndLine()
+ {
+ $formatter = new ChromePHPFormatter();
+ $record = array(
+ 'level' => Logger::CRITICAL,
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14),
+ 'message' => 'log',
+ );
+
+ $message = $formatter->format($record);
+
+ $this->assertEquals(
+ array(
+ 'meh',
+ array(
+ 'message' => 'log',
+ 'context' => array('from' => 'logger'),
+ 'extra' => array('ip' => '127.0.0.1'),
+ ),
+ 'test : 14',
+ 'error',
+ ),
+ $message
+ );
+ }
+
+ /**
+ * @covers Monolog\Formatter\ChromePHPFormatter::format
+ */
+ public function testFormatWithoutContext()
+ {
+ $formatter = new ChromePHPFormatter();
+ $record = array(
+ 'level' => Logger::DEBUG,
+ 'level_name' => 'DEBUG',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ $message = $formatter->format($record);
+
+ $this->assertEquals(
+ array(
+ 'meh',
+ 'log',
+ 'unknown',
+ 'log',
+ ),
+ $message
+ );
+ }
+
+ /**
+ * @covers Monolog\Formatter\ChromePHPFormatter::formatBatch
+ */
+ public function testBatchFormatThrowException()
+ {
+ $formatter = new ChromePHPFormatter();
+ $records = array(
+ array(
+ 'level' => Logger::INFO,
+ 'level_name' => 'INFO',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ ),
+ array(
+ 'level' => Logger::WARNING,
+ 'level_name' => 'WARNING',
+ 'channel' => 'foo',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log2',
+ ),
+ );
+
+ $this->assertEquals(
+ array(
+ array(
+ 'meh',
+ 'log',
+ 'unknown',
+ 'info',
+ ),
+ array(
+ 'foo',
+ 'log2',
+ 'unknown',
+ 'warn',
+ ),
+ ),
+ $formatter->formatBatch($records)
+ );
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php
new file mode 100644
index 0000000..90cc48d
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+
+class ElasticaFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ if (!class_exists("Elastica\Document")) {
+ $this->markTestSkipped("ruflin/elastica not installed");
+ }
+ }
+
+ /**
+ * @covers Monolog\Formatter\ElasticaFormatter::__construct
+ * @covers Monolog\Formatter\ElasticaFormatter::format
+ * @covers Monolog\Formatter\ElasticaFormatter::getDocument
+ */
+ public function testFormat()
+ {
+ // test log message
+ $msg = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ // expected values
+ $expected = $msg;
+ $expected['datetime'] = '1970-01-01T00:00:00.000000+00:00';
+ $expected['context'] = array(
+ 'class' => '[object] (stdClass: {})',
+ 'foo' => 7,
+ 0 => 'bar',
+ );
+
+ // format log message
+ $formatter = new ElasticaFormatter('my_index', 'doc_type');
+ $doc = $formatter->format($msg);
+ $this->assertInstanceOf('Elastica\Document', $doc);
+
+ // Document parameters
+ $params = $doc->getParams();
+ $this->assertEquals('my_index', $params['_index']);
+ $this->assertEquals('doc_type', $params['_type']);
+
+ // Document data values
+ $data = $doc->getData();
+ foreach (array_keys($expected) as $key) {
+ $this->assertEquals($expected[$key], $data[$key]);
+ }
+ }
+
+ /**
+ * @covers Monolog\Formatter\ElasticaFormatter::getIndex
+ * @covers Monolog\Formatter\ElasticaFormatter::getType
+ */
+ public function testGetters()
+ {
+ $formatter = new ElasticaFormatter('my_index', 'doc_type');
+ $this->assertEquals('my_index', $formatter->getIndex());
+ $this->assertEquals('doc_type', $formatter->getType());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php
new file mode 100644
index 0000000..1b2fd97
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+use Monolog\TestCase;
+
+class FlowdockFormatterTest extends TestCase
+{
+ /**
+ * @covers Monolog\Formatter\FlowdockFormatter::format
+ */
+ public function testFormat()
+ {
+ $formatter = new FlowdockFormatter('test_source', 'source@test.com');
+ $record = $this->getRecord();
+
+ $expected = array(
+ 'source' => 'test_source',
+ 'from_address' => 'source@test.com',
+ 'subject' => 'in test_source: WARNING - test',
+ 'content' => 'test',
+ 'tags' => array('#logs', '#warning', '#test'),
+ 'project' => 'test_source',
+ );
+ $formatted = $formatter->format($record);
+
+ $this->assertEquals($expected, $formatted['flowdock']);
+ }
+
+ /**
+ * @ covers Monolog\Formatter\FlowdockFormatter::formatBatch
+ */
+ public function testFormatBatch()
+ {
+ $formatter = new FlowdockFormatter('test_source', 'source@test.com');
+ $records = array(
+ $this->getRecord(Logger::WARNING),
+ $this->getRecord(Logger::DEBUG),
+ );
+ $formatted = $formatter->formatBatch($records);
+
+ $this->assertArrayHasKey('flowdock', $formatted[0]);
+ $this->assertArrayHasKey('flowdock', $formatted[1]);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php
new file mode 100644
index 0000000..fd36dbc
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+use Monolog\TestCase;
+
+class FluentdFormatterTest extends TestCase
+{
+ /**
+ * @covers Monolog\Formatter\FluentdFormatter::__construct
+ * @covers Monolog\Formatter\FluentdFormatter::isUsingLevelsInTag
+ */
+ public function testConstruct()
+ {
+ $formatter = new FluentdFormatter();
+ $this->assertEquals(false, $formatter->isUsingLevelsInTag());
+ $formatter = new FluentdFormatter(false);
+ $this->assertEquals(false, $formatter->isUsingLevelsInTag());
+ $formatter = new FluentdFormatter(true);
+ $this->assertEquals(true, $formatter->isUsingLevelsInTag());
+ }
+
+ /**
+ * @covers Monolog\Formatter\FluentdFormatter::format
+ */
+ public function testFormat()
+ {
+ $record = $this->getRecord(Logger::WARNING);
+ $record['datetime'] = new \DateTime("@0");
+
+ $formatter = new FluentdFormatter();
+ $this->assertEquals(
+ '["test",0,{"message":"test","context":[],"extra":[],"level":300,"level_name":"WARNING"}]',
+ $formatter->format($record)
+ );
+ }
+
+ /**
+ * @covers Monolog\Formatter\FluentdFormatter::format
+ */
+ public function testFormatWithTag()
+ {
+ $record = $this->getRecord(Logger::ERROR);
+ $record['datetime'] = new \DateTime("@0");
+
+ $formatter = new FluentdFormatter(true);
+ $this->assertEquals(
+ '["test.error",0,{"message":"test","context":[],"extra":[]}]',
+ $formatter->format($record)
+ );
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php
new file mode 100644
index 0000000..4a24761
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php
@@ -0,0 +1,258 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+
+class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ if (!class_exists('\Gelf\Message')) {
+ $this->markTestSkipped("graylog2/gelf-php or mlehner/gelf-php is not installed");
+ }
+ }
+
+ /**
+ * @covers Monolog\Formatter\GelfMessageFormatter::format
+ */
+ public function testDefaultFormatter()
+ {
+ $formatter = new GelfMessageFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ $message = $formatter->format($record);
+
+ $this->assertInstanceOf('Gelf\Message', $message);
+ $this->assertEquals(0, $message->getTimestamp());
+ $this->assertEquals('log', $message->getShortMessage());
+ $this->assertEquals('meh', $message->getFacility());
+ $this->assertEquals(null, $message->getLine());
+ $this->assertEquals(null, $message->getFile());
+ $this->assertEquals($this->isLegacy() ? 3 : 'error', $message->getLevel());
+ $this->assertNotEmpty($message->getHost());
+
+ $formatter = new GelfMessageFormatter('mysystem');
+
+ $message = $formatter->format($record);
+
+ $this->assertInstanceOf('Gelf\Message', $message);
+ $this->assertEquals('mysystem', $message->getHost());
+ }
+
+ /**
+ * @covers Monolog\Formatter\GelfMessageFormatter::format
+ */
+ public function testFormatWithFileAndLine()
+ {
+ $formatter = new GelfMessageFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('file' => 'test', 'line' => 14),
+ 'message' => 'log',
+ );
+
+ $message = $formatter->format($record);
+
+ $this->assertInstanceOf('Gelf\Message', $message);
+ $this->assertEquals('test', $message->getFile());
+ $this->assertEquals(14, $message->getLine());
+ }
+
+ /**
+ * @covers Monolog\Formatter\GelfMessageFormatter::format
+ * @expectedException InvalidArgumentException
+ */
+ public function testFormatInvalidFails()
+ {
+ $formatter = new GelfMessageFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ );
+
+ $formatter->format($record);
+ }
+
+ /**
+ * @covers Monolog\Formatter\GelfMessageFormatter::format
+ */
+ public function testFormatWithContext()
+ {
+ $formatter = new GelfMessageFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => 'pair'),
+ 'message' => 'log',
+ );
+
+ $message = $formatter->format($record);
+
+ $this->assertInstanceOf('Gelf\Message', $message);
+
+ $message_array = $message->toArray();
+
+ $this->assertArrayHasKey('_ctxt_from', $message_array);
+ $this->assertEquals('logger', $message_array['_ctxt_from']);
+
+ // Test with extraPrefix
+ $formatter = new GelfMessageFormatter(null, null, 'CTX');
+ $message = $formatter->format($record);
+
+ $this->assertInstanceOf('Gelf\Message', $message);
+
+ $message_array = $message->toArray();
+
+ $this->assertArrayHasKey('_CTXfrom', $message_array);
+ $this->assertEquals('logger', $message_array['_CTXfrom']);
+ }
+
+ /**
+ * @covers Monolog\Formatter\GelfMessageFormatter::format
+ */
+ public function testFormatWithContextContainingException()
+ {
+ $formatter = new GelfMessageFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger', 'exception' => array(
+ 'class' => '\Exception',
+ 'file' => '/some/file/in/dir.php:56',
+ 'trace' => array('/some/file/1.php:23', '/some/file/2.php:3'),
+ )),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ $message = $formatter->format($record);
+
+ $this->assertInstanceOf('Gelf\Message', $message);
+
+ $this->assertEquals("/some/file/in/dir.php", $message->getFile());
+ $this->assertEquals("56", $message->getLine());
+ }
+
+ /**
+ * @covers Monolog\Formatter\GelfMessageFormatter::format
+ */
+ public function testFormatWithExtra()
+ {
+ $formatter = new GelfMessageFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => 'pair'),
+ 'message' => 'log',
+ );
+
+ $message = $formatter->format($record);
+
+ $this->assertInstanceOf('Gelf\Message', $message);
+
+ $message_array = $message->toArray();
+
+ $this->assertArrayHasKey('_key', $message_array);
+ $this->assertEquals('pair', $message_array['_key']);
+
+ // Test with extraPrefix
+ $formatter = new GelfMessageFormatter(null, 'EXT');
+ $message = $formatter->format($record);
+
+ $this->assertInstanceOf('Gelf\Message', $message);
+
+ $message_array = $message->toArray();
+
+ $this->assertArrayHasKey('_EXTkey', $message_array);
+ $this->assertEquals('pair', $message_array['_EXTkey']);
+ }
+
+ public function testFormatWithLargeData()
+ {
+ $formatter = new GelfMessageFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('exception' => str_repeat(' ', 32767)),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => str_repeat(' ', 32767)),
+ 'message' => 'log'
+ );
+ $message = $formatter->format($record);
+ $messageArray = $message->toArray();
+
+ // 200 for padding + metadata
+ $length = 200;
+
+ foreach ($messageArray as $key => $value) {
+ if (!in_array($key, array('level', 'timestamp'))) {
+ $length += strlen($value);
+ }
+ }
+
+ $this->assertLessThanOrEqual(65792, $length, 'The message length is no longer than the maximum allowed length');
+ }
+
+ public function testFormatWithUnlimitedLength()
+ {
+ $formatter = new GelfMessageFormatter('LONG_SYSTEM_NAME', null, 'ctxt_', PHP_INT_MAX);
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('exception' => str_repeat(' ', 32767 * 2)),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => str_repeat(' ', 32767 * 2)),
+ 'message' => 'log'
+ );
+ $message = $formatter->format($record);
+ $messageArray = $message->toArray();
+
+ // 200 for padding + metadata
+ $length = 200;
+
+ foreach ($messageArray as $key => $value) {
+ if (!in_array($key, array('level', 'timestamp'))) {
+ $length += strlen($value);
+ }
+ }
+
+ $this->assertGreaterThanOrEqual(131289, $length, 'The message should not be truncated');
+ }
+
+ private function isLegacy()
+ {
+ return interface_exists('\Gelf\IMessagePublisher');
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php
new file mode 100644
index 0000000..24b06cc
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php
@@ -0,0 +1,219 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+use Monolog\TestCase;
+
+class JsonFormatterTest extends TestCase
+{
+ /**
+ * @covers Monolog\Formatter\JsonFormatter::__construct
+ * @covers Monolog\Formatter\JsonFormatter::getBatchMode
+ * @covers Monolog\Formatter\JsonFormatter::isAppendingNewlines
+ */
+ public function testConstruct()
+ {
+ $formatter = new JsonFormatter();
+ $this->assertEquals(JsonFormatter::BATCH_MODE_JSON, $formatter->getBatchMode());
+ $this->assertEquals(true, $formatter->isAppendingNewlines());
+ $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES, false);
+ $this->assertEquals(JsonFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode());
+ $this->assertEquals(false, $formatter->isAppendingNewlines());
+ }
+
+ /**
+ * @covers Monolog\Formatter\JsonFormatter::format
+ */
+ public function testFormat()
+ {
+ $formatter = new JsonFormatter();
+ $record = $this->getRecord();
+ $this->assertEquals(json_encode($record)."\n", $formatter->format($record));
+
+ $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
+ $record = $this->getRecord();
+ $this->assertEquals(json_encode($record), $formatter->format($record));
+ }
+
+ /**
+ * @covers Monolog\Formatter\JsonFormatter::formatBatch
+ * @covers Monolog\Formatter\JsonFormatter::formatBatchJson
+ */
+ public function testFormatBatch()
+ {
+ $formatter = new JsonFormatter();
+ $records = array(
+ $this->getRecord(Logger::WARNING),
+ $this->getRecord(Logger::DEBUG),
+ );
+ $this->assertEquals(json_encode($records), $formatter->formatBatch($records));
+ }
+
+ /**
+ * @covers Monolog\Formatter\JsonFormatter::formatBatch
+ * @covers Monolog\Formatter\JsonFormatter::formatBatchNewlines
+ */
+ public function testFormatBatchNewlines()
+ {
+ $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES);
+ $records = $expected = array(
+ $this->getRecord(Logger::WARNING),
+ $this->getRecord(Logger::DEBUG),
+ );
+ array_walk($expected, function (&$value, $key) {
+ $value = json_encode($value);
+ });
+ $this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records));
+ }
+
+ public function testDefFormatWithException()
+ {
+ $formatter = new JsonFormatter();
+ $exception = new \RuntimeException('Foo');
+ $formattedException = $this->formatException($exception);
+
+ $message = $this->formatRecordWithExceptionInContext($formatter, $exception);
+
+ $this->assertContextContainsFormattedException($formattedException, $message);
+ }
+
+ public function testDefFormatWithPreviousException()
+ {
+ $formatter = new JsonFormatter();
+ $exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?'));
+ $formattedPrevException = $this->formatException($exception->getPrevious());
+ $formattedException = $this->formatException($exception, $formattedPrevException);
+
+ $message = $this->formatRecordWithExceptionInContext($formatter, $exception);
+
+ $this->assertContextContainsFormattedException($formattedException, $message);
+ }
+
+ public function testDefFormatWithThrowable()
+ {
+ if (!class_exists('Error') || !is_subclass_of('Error', 'Throwable')) {
+ $this->markTestSkipped('Requires PHP >=7');
+ }
+
+ $formatter = new JsonFormatter();
+ $throwable = new \Error('Foo');
+ $formattedThrowable = $this->formatException($throwable);
+
+ $message = $this->formatRecordWithExceptionInContext($formatter, $throwable);
+
+ $this->assertContextContainsFormattedException($formattedThrowable, $message);
+ }
+
+ /**
+ * @param string $expected
+ * @param string $actual
+ *
+ * @internal param string $exception
+ */
+ private function assertContextContainsFormattedException($expected, $actual)
+ {
+ $this->assertEquals(
+ '{"level_name":"CRITICAL","channel":"core","context":{"exception":'.$expected.'},"datetime":null,"extra":[],"message":"foobar"}'."\n",
+ $actual
+ );
+ }
+
+ /**
+ * @param JsonFormatter $formatter
+ * @param \Exception|\Throwable $exception
+ *
+ * @return string
+ */
+ private function formatRecordWithExceptionInContext(JsonFormatter $formatter, $exception)
+ {
+ $message = $formatter->format(array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'core',
+ 'context' => array('exception' => $exception),
+ 'datetime' => null,
+ 'extra' => array(),
+ 'message' => 'foobar',
+ ));
+ return $message;
+ }
+
+ /**
+ * @param \Exception|\Throwable $exception
+ *
+ * @return string
+ */
+ private function formatExceptionFilePathWithLine($exception)
+ {
+ $options = 0;
+ if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
+ $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
+ }
+ $path = substr(json_encode($exception->getFile(), $options), 1, -1);
+ return $path . ':' . $exception->getLine();
+ }
+
+ /**
+ * @param \Exception|\Throwable $exception
+ *
+ * @param null|string $previous
+ *
+ * @return string
+ */
+ private function formatException($exception, $previous = null)
+ {
+ $formattedException =
+ '{"class":"' . get_class($exception) .
+ '","message":"' . $exception->getMessage() .
+ '","code":' . $exception->getCode() .
+ ',"file":"' . $this->formatExceptionFilePathWithLine($exception) .
+ ($previous ? '","previous":' . $previous : '"') .
+ '}';
+ 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/core/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php
new file mode 100644
index 0000000..310d93c
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php
@@ -0,0 +1,222 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+/**
+ * @covers Monolog\Formatter\LineFormatter
+ */
+class LineFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ public function testDefFormatWithString()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d');
+ $message = $formatter->format(array(
+ 'level_name' => 'WARNING',
+ 'channel' => 'log',
+ 'context' => array(),
+ 'message' => 'foo',
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ));
+ $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message);
+ }
+
+ public function testDefFormatWithArrayContext()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d');
+ $message = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'message' => 'foo',
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ 'context' => array(
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ 'bool' => false,
+ 'null' => null,
+ ),
+ ));
+ $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux","bool":false,"null":null} []'."\n", $message);
+ }
+
+ public function testDefFormatExtras()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d');
+ $message = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array('ip' => '127.0.0.1'),
+ 'message' => 'log',
+ ));
+ $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {"ip":"127.0.0.1"}'."\n", $message);
+ }
+
+ public function testFormatExtras()
+ {
+ $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\n", 'Y-m-d');
+ $message = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array('ip' => '127.0.0.1', 'file' => 'test'),
+ 'message' => 'log',
+ ));
+ $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {"ip":"127.0.0.1"}'."\n", $message);
+ }
+
+ public function testContextAndExtraOptionallyNotShownIfEmpty()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d', false, true);
+ $message = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ 'message' => 'log',
+ ));
+ $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message);
+ }
+
+ public function testContextAndExtraReplacement()
+ {
+ $formatter = new LineFormatter('%context.foo% => %extra.foo%');
+ $message = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('foo' => 'bar'),
+ 'datetime' => new \DateTime,
+ 'extra' => array('foo' => 'xbar'),
+ 'message' => 'log',
+ ));
+ $this->assertEquals('bar => xbar', $message);
+ }
+
+ public function testDefFormatWithObject()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d');
+ $message = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')),
+ 'message' => 'foobar',
+ ));
+
+ $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: bar)","baz":[],"res":"[resource] (stream)"}'."\n", $message);
+ }
+
+ public function testDefFormatWithException()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d');
+ $message = $formatter->format(array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'core',
+ 'context' => array('exception' => new \RuntimeException('Foo')),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ 'message' => 'foobar',
+ ));
+
+ $path = str_replace('\\/', '/', json_encode(__FILE__));
+
+ $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).')"} []'."\n", $message);
+ }
+
+ public function testDefFormatWithPreviousException()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d');
+ $previous = new \LogicException('Wut?');
+ $message = $formatter->format(array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'core',
+ 'context' => array('exception' => new \RuntimeException('Foo', 0, $previous)),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ 'message' => 'foobar',
+ ));
+
+ $path = str_replace('\\/', '/', json_encode(__FILE__));
+
+ $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).', LogicException(code: 0): Wut? at '.substr($path, 1, -1).':'.(__LINE__ - 12).')"} []'."\n", $message);
+ }
+
+ public function testBatchFormat()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d');
+ $message = $formatter->formatBatch(array(
+ array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'test',
+ 'message' => 'bar',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ),
+ array(
+ 'level_name' => 'WARNING',
+ 'channel' => 'log',
+ 'message' => 'foo',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ),
+ ));
+ $this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'."\n".'['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message);
+ }
+
+ public function testFormatShouldStripInlineLineBreaks()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d');
+ $message = $formatter->format(
+ array(
+ 'message' => "foo\nbar",
+ 'context' => array(),
+ 'extra' => array(),
+ )
+ );
+
+ $this->assertRegExp('/foo bar/', $message);
+ }
+
+ public function testFormatShouldNotStripInlineLineBreaksWhenFlagIsSet()
+ {
+ $formatter = new LineFormatter(null, 'Y-m-d', true);
+ $message = $formatter->format(
+ array(
+ 'message' => "foo\nbar",
+ 'context' => array(),
+ 'extra' => array(),
+ )
+ );
+
+ $this->assertRegExp('/foo\nbar/', $message);
+ }
+}
+
+class TestFoo
+{
+ public $foo = 'foo';
+}
+
+class TestBar
+{
+ public function __toString()
+ {
+ return 'bar';
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php
new file mode 100644
index 0000000..6d59b3f
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\TestCase;
+
+class LogglyFormatterTest extends TestCase
+{
+ /**
+ * @covers Monolog\Formatter\LogglyFormatter::__construct
+ */
+ public function testConstruct()
+ {
+ $formatter = new LogglyFormatter();
+ $this->assertEquals(LogglyFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode());
+ $formatter = new LogglyFormatter(LogglyFormatter::BATCH_MODE_JSON);
+ $this->assertEquals(LogglyFormatter::BATCH_MODE_JSON, $formatter->getBatchMode());
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogglyFormatter::format
+ */
+ public function testFormat()
+ {
+ $formatter = new LogglyFormatter();
+ $record = $this->getRecord();
+ $formatted_decoded = json_decode($formatter->format($record), true);
+ $this->assertArrayHasKey("timestamp", $formatted_decoded);
+ $this->assertEquals(new \DateTime($formatted_decoded["timestamp"]), $record["datetime"]);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php
new file mode 100644
index 0000000..9f6b1cc
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php
@@ -0,0 +1,333 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+
+class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ public function tearDown()
+ {
+ \PHPUnit_Framework_Error_Warning::$enabled = true;
+
+ return parent::tearDown();
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogstashFormatter::format
+ */
+ public function testDefaultFormatter()
+ {
+ $formatter = new LogstashFormatter('test', 'hostname');
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']);
+ $this->assertEquals('log', $message['@message']);
+ $this->assertEquals('meh', $message['@fields']['channel']);
+ $this->assertContains('meh', $message['@tags']);
+ $this->assertEquals(Logger::ERROR, $message['@fields']['level']);
+ $this->assertEquals('test', $message['@type']);
+ $this->assertEquals('hostname', $message['@source']);
+
+ $formatter = new LogstashFormatter('mysystem');
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertEquals('mysystem', $message['@type']);
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogstashFormatter::format
+ */
+ public function testFormatWithFileAndLine()
+ {
+ $formatter = new LogstashFormatter('test');
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('file' => 'test', 'line' => 14),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertEquals('test', $message['@fields']['file']);
+ $this->assertEquals(14, $message['@fields']['line']);
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogstashFormatter::format
+ */
+ public function testFormatWithContext()
+ {
+ $formatter = new LogstashFormatter('test');
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => 'pair'),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $message_array = $message['@fields'];
+
+ $this->assertArrayHasKey('ctxt_from', $message_array);
+ $this->assertEquals('logger', $message_array['ctxt_from']);
+
+ // Test with extraPrefix
+ $formatter = new LogstashFormatter('test', null, null, 'CTX');
+ $message = json_decode($formatter->format($record), true);
+
+ $message_array = $message['@fields'];
+
+ $this->assertArrayHasKey('CTXfrom', $message_array);
+ $this->assertEquals('logger', $message_array['CTXfrom']);
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogstashFormatter::format
+ */
+ public function testFormatWithExtra()
+ {
+ $formatter = new LogstashFormatter('test');
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => 'pair'),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $message_array = $message['@fields'];
+
+ $this->assertArrayHasKey('key', $message_array);
+ $this->assertEquals('pair', $message_array['key']);
+
+ // Test with extraPrefix
+ $formatter = new LogstashFormatter('test', null, 'EXT');
+ $message = json_decode($formatter->format($record), true);
+
+ $message_array = $message['@fields'];
+
+ $this->assertArrayHasKey('EXTkey', $message_array);
+ $this->assertEquals('pair', $message_array['EXTkey']);
+ }
+
+ public function testFormatWithApplicationName()
+ {
+ $formatter = new LogstashFormatter('app', 'test');
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => 'pair'),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertArrayHasKey('@type', $message);
+ $this->assertEquals('app', $message['@type']);
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogstashFormatter::format
+ */
+ public function testDefaultFormatterV1()
+ {
+ $formatter = new LogstashFormatter('test', 'hostname', null, 'ctxt_', LogstashFormatter::V1);
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']);
+ $this->assertEquals("1", $message['@version']);
+ $this->assertEquals('log', $message['message']);
+ $this->assertEquals('meh', $message['channel']);
+ $this->assertEquals('ERROR', $message['level']);
+ $this->assertEquals('test', $message['type']);
+ $this->assertEquals('hostname', $message['host']);
+
+ $formatter = new LogstashFormatter('mysystem', null, null, 'ctxt_', LogstashFormatter::V1);
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertEquals('mysystem', $message['type']);
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogstashFormatter::format
+ */
+ public function testFormatWithFileAndLineV1()
+ {
+ $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1);
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('file' => 'test', 'line' => 14),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertEquals('test', $message['file']);
+ $this->assertEquals(14, $message['line']);
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogstashFormatter::format
+ */
+ public function testFormatWithContextV1()
+ {
+ $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1);
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => 'pair'),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertArrayHasKey('ctxt_from', $message);
+ $this->assertEquals('logger', $message['ctxt_from']);
+
+ // Test with extraPrefix
+ $formatter = new LogstashFormatter('test', null, null, 'CTX', LogstashFormatter::V1);
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertArrayHasKey('CTXfrom', $message);
+ $this->assertEquals('logger', $message['CTXfrom']);
+ }
+
+ /**
+ * @covers Monolog\Formatter\LogstashFormatter::format
+ */
+ public function testFormatWithExtraV1()
+ {
+ $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1);
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => 'pair'),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertArrayHasKey('key', $message);
+ $this->assertEquals('pair', $message['key']);
+
+ // Test with extraPrefix
+ $formatter = new LogstashFormatter('test', null, 'EXT', 'ctxt_', LogstashFormatter::V1);
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertArrayHasKey('EXTkey', $message);
+ $this->assertEquals('pair', $message['EXTkey']);
+ }
+
+ public function testFormatWithApplicationNameV1()
+ {
+ $formatter = new LogstashFormatter('app', 'test', null, 'ctxt_', LogstashFormatter::V1);
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('key' => 'pair'),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertArrayHasKey('type', $message);
+ $this->assertEquals('app', $message['type']);
+ }
+
+ public function testFormatWithLatin9Data()
+ {
+ if (version_compare(PHP_VERSION, '5.5.0', '<')) {
+ // Ignore the warning that will be emitted by PHP <5.5.0
+ \PHPUnit_Framework_Error_Warning::$enabled = false;
+ }
+ $formatter = new LogstashFormatter('test', 'hostname');
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => '¯\_(ツ)_/¯',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(
+ 'user_agent' => "\xD6WN; FBCR/OrangeEspa\xF1a; Vers\xE3o/4.0; F\xE4rist",
+ ),
+ 'message' => 'log',
+ );
+
+ $message = json_decode($formatter->format($record), true);
+
+ $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']);
+ $this->assertEquals('log', $message['@message']);
+ $this->assertEquals('¯\_(ツ)_/¯', $message['@fields']['channel']);
+ $this->assertContains('¯\_(ツ)_/¯', $message['@tags']);
+ $this->assertEquals(Logger::ERROR, $message['@fields']['level']);
+ $this->assertEquals('test', $message['@type']);
+ $this->assertEquals('hostname', $message['@source']);
+ if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
+ $this->assertEquals('ÖWN; FBCR/OrangeEspaña; Versão/4.0; Färist', $message['@fields']['user_agent']);
+ } else {
+ // PHP <5.5 does not return false for an element encoding failure,
+ // instead it emits a warning (possibly) and nulls the value.
+ $this->assertEquals(null, $message['@fields']['user_agent']);
+ }
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php
new file mode 100644
index 0000000..52e699e
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php
@@ -0,0 +1,262 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+
+/**
+ * @author Florian Plattner
+ */
+class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ if (!class_exists('MongoDate')) {
+ $this->markTestSkipped('mongo extension not installed');
+ }
+ }
+
+ public function constructArgumentProvider()
+ {
+ return array(
+ array(1, true, 1, true),
+ array(0, false, 0, false),
+ );
+ }
+
+ /**
+ * @param $traceDepth
+ * @param $traceAsString
+ * @param $expectedTraceDepth
+ * @param $expectedTraceAsString
+ *
+ * @dataProvider constructArgumentProvider
+ */
+ public function testConstruct($traceDepth, $traceAsString, $expectedTraceDepth, $expectedTraceAsString)
+ {
+ $formatter = new MongoDBFormatter($traceDepth, $traceAsString);
+
+ $reflTrace = new \ReflectionProperty($formatter, 'exceptionTraceAsString');
+ $reflTrace->setAccessible(true);
+ $this->assertEquals($expectedTraceAsString, $reflTrace->getValue($formatter));
+
+ $reflDepth = new\ReflectionProperty($formatter, 'maxNestingLevel');
+ $reflDepth->setAccessible(true);
+ $this->assertEquals($expectedTraceDepth, $reflDepth->getValue($formatter));
+ }
+
+ public function testSimpleFormat()
+ {
+ $record = array(
+ 'message' => 'some log message',
+ 'context' => array(),
+ 'level' => Logger::WARNING,
+ 'level_name' => Logger::getLevelName(Logger::WARNING),
+ 'channel' => 'test',
+ 'datetime' => new \DateTime('2014-02-01 00:00:00'),
+ 'extra' => array(),
+ );
+
+ $formatter = new MongoDBFormatter();
+ $formattedRecord = $formatter->format($record);
+
+ $this->assertCount(7, $formattedRecord);
+ $this->assertEquals('some log message', $formattedRecord['message']);
+ $this->assertEquals(array(), $formattedRecord['context']);
+ $this->assertEquals(Logger::WARNING, $formattedRecord['level']);
+ $this->assertEquals(Logger::getLevelName(Logger::WARNING), $formattedRecord['level_name']);
+ $this->assertEquals('test', $formattedRecord['channel']);
+ $this->assertInstanceOf('\MongoDate', $formattedRecord['datetime']);
+ $this->assertEquals('0.00000000 1391212800', $formattedRecord['datetime']->__toString());
+ $this->assertEquals(array(), $formattedRecord['extra']);
+ }
+
+ public function testRecursiveFormat()
+ {
+ $someObject = new \stdClass();
+ $someObject->foo = 'something';
+ $someObject->bar = 'stuff';
+
+ $record = array(
+ 'message' => 'some log message',
+ 'context' => array(
+ 'stuff' => new \DateTime('2014-02-01 02:31:33'),
+ 'some_object' => $someObject,
+ 'context_string' => 'some string',
+ 'context_int' => 123456,
+ 'except' => new \Exception('exception message', 987),
+ ),
+ 'level' => Logger::WARNING,
+ 'level_name' => Logger::getLevelName(Logger::WARNING),
+ 'channel' => 'test',
+ 'datetime' => new \DateTime('2014-02-01 00:00:00'),
+ 'extra' => array(),
+ );
+
+ $formatter = new MongoDBFormatter();
+ $formattedRecord = $formatter->format($record);
+
+ $this->assertCount(5, $formattedRecord['context']);
+ $this->assertInstanceOf('\MongoDate', $formattedRecord['context']['stuff']);
+ $this->assertEquals('0.00000000 1391221893', $formattedRecord['context']['stuff']->__toString());
+ $this->assertEquals(
+ array(
+ 'foo' => 'something',
+ 'bar' => 'stuff',
+ 'class' => 'stdClass',
+ ),
+ $formattedRecord['context']['some_object']
+ );
+ $this->assertEquals('some string', $formattedRecord['context']['context_string']);
+ $this->assertEquals(123456, $formattedRecord['context']['context_int']);
+
+ $this->assertCount(5, $formattedRecord['context']['except']);
+ $this->assertEquals('exception message', $formattedRecord['context']['except']['message']);
+ $this->assertEquals(987, $formattedRecord['context']['except']['code']);
+ $this->assertInternalType('string', $formattedRecord['context']['except']['file']);
+ $this->assertInternalType('integer', $formattedRecord['context']['except']['code']);
+ $this->assertInternalType('string', $formattedRecord['context']['except']['trace']);
+ $this->assertEquals('Exception', $formattedRecord['context']['except']['class']);
+ }
+
+ public function testFormatDepthArray()
+ {
+ $record = array(
+ 'message' => 'some log message',
+ 'context' => array(
+ 'nest2' => array(
+ 'property' => 'anything',
+ 'nest3' => array(
+ 'nest4' => 'value',
+ 'property' => 'nothing',
+ ),
+ ),
+ ),
+ 'level' => Logger::WARNING,
+ 'level_name' => Logger::getLevelName(Logger::WARNING),
+ 'channel' => 'test',
+ 'datetime' => new \DateTime('2014-02-01 00:00:00'),
+ 'extra' => array(),
+ );
+
+ $formatter = new MongoDBFormatter(2);
+ $formattedResult = $formatter->format($record);
+
+ $this->assertEquals(
+ array(
+ 'nest2' => array(
+ 'property' => 'anything',
+ 'nest3' => '[...]',
+ ),
+ ),
+ $formattedResult['context']
+ );
+ }
+
+ public function testFormatDepthArrayInfiniteNesting()
+ {
+ $record = array(
+ 'message' => 'some log message',
+ 'context' => array(
+ 'nest2' => array(
+ 'property' => 'something',
+ 'nest3' => array(
+ 'property' => 'anything',
+ 'nest4' => array(
+ 'property' => 'nothing',
+ ),
+ ),
+ ),
+ ),
+ 'level' => Logger::WARNING,
+ 'level_name' => Logger::getLevelName(Logger::WARNING),
+ 'channel' => 'test',
+ 'datetime' => new \DateTime('2014-02-01 00:00:00'),
+ 'extra' => array(),
+ );
+
+ $formatter = new MongoDBFormatter(0);
+ $formattedResult = $formatter->format($record);
+
+ $this->assertEquals(
+ array(
+ 'nest2' => array(
+ 'property' => 'something',
+ 'nest3' => array(
+ 'property' => 'anything',
+ 'nest4' => array(
+ 'property' => 'nothing',
+ ),
+ ),
+ ),
+ ),
+ $formattedResult['context']
+ );
+ }
+
+ public function testFormatDepthObjects()
+ {
+ $someObject = new \stdClass();
+ $someObject->property = 'anything';
+ $someObject->nest3 = new \stdClass();
+ $someObject->nest3->property = 'nothing';
+ $someObject->nest3->nest4 = 'invisible';
+
+ $record = array(
+ 'message' => 'some log message',
+ 'context' => array(
+ 'nest2' => $someObject,
+ ),
+ 'level' => Logger::WARNING,
+ 'level_name' => Logger::getLevelName(Logger::WARNING),
+ 'channel' => 'test',
+ 'datetime' => new \DateTime('2014-02-01 00:00:00'),
+ 'extra' => array(),
+ );
+
+ $formatter = new MongoDBFormatter(2, true);
+ $formattedResult = $formatter->format($record);
+
+ $this->assertEquals(
+ array(
+ 'nest2' => array(
+ 'property' => 'anything',
+ 'nest3' => '[...]',
+ 'class' => 'stdClass',
+ ),
+ ),
+ $formattedResult['context']
+ );
+ }
+
+ public function testFormatDepthException()
+ {
+ $record = array(
+ 'message' => 'some log message',
+ 'context' => array(
+ 'nest2' => new \Exception('exception message', 987),
+ ),
+ 'level' => Logger::WARNING,
+ 'level_name' => Logger::getLevelName(Logger::WARNING),
+ 'channel' => 'test',
+ 'datetime' => new \DateTime('2014-02-01 00:00:00'),
+ 'extra' => array(),
+ );
+
+ $formatter = new MongoDBFormatter(2, false);
+ $formattedRecord = $formatter->format($record);
+
+ $this->assertEquals('exception message', $formattedRecord['context']['nest2']['message']);
+ $this->assertEquals(987, $formattedRecord['context']['nest2']['code']);
+ $this->assertEquals('[...]', $formattedRecord['context']['nest2']['trace']);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php
new file mode 100644
index 0000000..bafd1c7
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php
@@ -0,0 +1,481 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+/**
+ * @covers Monolog\Formatter\NormalizerFormatter
+ */
+class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ public function tearDown()
+ {
+ \PHPUnit_Framework_Error_Warning::$enabled = true;
+
+ return parent::tearDown();
+ }
+
+ public function testFormat()
+ {
+ $formatter = new NormalizerFormatter('Y-m-d');
+ $formatted = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'message' => 'foo',
+ 'datetime' => new \DateTime,
+ 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')),
+ 'context' => array(
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ 'inf' => INF,
+ '-inf' => -INF,
+ 'nan' => acos(4),
+ ),
+ ));
+
+ $this->assertEquals(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'message' => 'foo',
+ 'datetime' => date('Y-m-d'),
+ 'extra' => array(
+ 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})',
+ 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: bar)',
+ 'baz' => array(),
+ 'res' => '[resource] (stream)',
+ ),
+ 'context' => array(
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ 'inf' => 'INF',
+ '-inf' => '-INF',
+ 'nan' => 'NaN',
+ ),
+ ), $formatted);
+ }
+
+ public function testFormatExceptions()
+ {
+ $formatter = new NormalizerFormatter('Y-m-d');
+ $e = new \LogicException('bar');
+ $e2 = new \RuntimeException('foo', 0, $e);
+ $formatted = $formatter->format(array(
+ 'exception' => $e2,
+ ));
+
+ $this->assertGreaterThan(5, count($formatted['exception']['trace']));
+ $this->assertTrue(isset($formatted['exception']['previous']));
+ unset($formatted['exception']['trace'], $formatted['exception']['previous']);
+
+ $this->assertEquals(array(
+ 'exception' => array(
+ 'class' => get_class($e2),
+ 'message' => $e2->getMessage(),
+ 'code' => $e2->getCode(),
+ 'file' => $e2->getFile().':'.$e2->getLine(),
+ ),
+ ), $formatted);
+ }
+
+ public function testFormatSoapFaultException()
+ {
+ if (!class_exists('SoapFault')) {
+ $this->markTestSkipped('Requires the soap extension');
+ }
+
+ $formatter = new NormalizerFormatter('Y-m-d');
+ $e = new \SoapFault('foo', 'bar', 'hello', 'world');
+ $formatted = $formatter->format(array(
+ 'exception' => $e,
+ ));
+
+ unset($formatted['exception']['trace']);
+
+ $this->assertEquals(array(
+ 'exception' => array(
+ 'class' => 'SoapFault',
+ 'message' => 'bar',
+ 'code' => 0,
+ 'file' => $e->getFile().':'.$e->getLine(),
+ 'faultcode' => 'foo',
+ 'faultactor' => 'hello',
+ 'detail' => 'world',
+ ),
+ ), $formatted);
+ }
+
+ public function testFormatToStringExceptionHandle()
+ {
+ $formatter = new NormalizerFormatter('Y-m-d');
+ $this->setExpectedException('RuntimeException', 'Could not convert to string');
+ $formatter->format(array(
+ 'myObject' => new TestToStringError(),
+ ));
+ }
+
+ public function testBatchFormat()
+ {
+ $formatter = new NormalizerFormatter('Y-m-d');
+ $formatted = $formatter->formatBatch(array(
+ array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'test',
+ 'message' => 'bar',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ),
+ array(
+ 'level_name' => 'WARNING',
+ 'channel' => 'log',
+ 'message' => 'foo',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ),
+ ));
+ $this->assertEquals(array(
+ array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'test',
+ 'message' => 'bar',
+ 'context' => array(),
+ 'datetime' => date('Y-m-d'),
+ 'extra' => array(),
+ ),
+ array(
+ 'level_name' => 'WARNING',
+ 'channel' => 'log',
+ 'message' => 'foo',
+ 'context' => array(),
+ 'datetime' => date('Y-m-d'),
+ 'extra' => array(),
+ ),
+ ), $formatted);
+ }
+
+ /**
+ * Test issue #137
+ */
+ public function testIgnoresRecursiveObjectReferences()
+ {
+ // set up the recursion
+ $foo = new \stdClass();
+ $bar = new \stdClass();
+
+ $foo->bar = $bar;
+ $bar->foo = $foo;
+
+ // set an error handler to assert that the error is not raised anymore
+ $that = $this;
+ set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {
+ if (error_reporting() & $level) {
+ restore_error_handler();
+ $that->fail("$message should not be raised");
+ }
+ });
+
+ $formatter = new NormalizerFormatter();
+ $reflMethod = new \ReflectionMethod($formatter, 'toJson');
+ $reflMethod->setAccessible(true);
+ $res = $reflMethod->invoke($formatter, array($foo, $bar), true);
+
+ restore_error_handler();
+
+ $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
+ $resource = fopen(__FILE__, 'r');
+
+ // set an error handler to assert that the error is not raised anymore
+ $that = $this;
+ set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {
+ if (error_reporting() & $level) {
+ restore_error_handler();
+ $that->fail("$message should not be raised");
+ }
+ });
+
+ $formatter = new NormalizerFormatter();
+ $reflMethod = new \ReflectionMethod($formatter, 'toJson');
+ $reflMethod->setAccessible(true);
+ $res = $reflMethod->invoke($formatter, array($resource), true);
+
+ restore_error_handler();
+
+ $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();
+ $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]['...']);
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testThrowsOnInvalidEncoding()
+ {
+ if (version_compare(PHP_VERSION, '5.5.0', '<')) {
+ // Ignore the warning that will be emitted by PHP <5.5.0
+ \PHPUnit_Framework_Error_Warning::$enabled = false;
+ }
+ $formatter = new NormalizerFormatter();
+ $reflMethod = new \ReflectionMethod($formatter, 'toJson');
+ $reflMethod->setAccessible(true);
+
+ // send an invalid unicode sequence as a object that can't be cleaned
+ $record = new \stdClass;
+ $record->message = "\xB1\x31";
+ $res = $reflMethod->invoke($formatter, $record);
+ if (PHP_VERSION_ID < 50500 && $res === '{"message":null}') {
+ throw new \RuntimeException('PHP 5.3/5.4 throw a warning and null the value instead of returning false entirely');
+ }
+ }
+
+ public function testConvertsInvalidEncodingAsLatin9()
+ {
+ if (version_compare(PHP_VERSION, '5.5.0', '<')) {
+ // Ignore the warning that will be emitted by PHP <5.5.0
+ \PHPUnit_Framework_Error_Warning::$enabled = false;
+ }
+ $formatter = new NormalizerFormatter();
+ $reflMethod = new \ReflectionMethod($formatter, 'toJson');
+ $reflMethod->setAccessible(true);
+
+ $res = $reflMethod->invoke($formatter, array('message' => "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE"));
+
+ if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
+ $this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res);
+ } else {
+ // PHP <5.5 does not return false for an element encoding failure,
+ // instead it emits a warning (possibly) and nulls the value.
+ $this->assertSame('{"message":null}', $res);
+ }
+ }
+
+ /**
+ * @param mixed $in Input
+ * @param mixed $expect Expected output
+ * @covers Monolog\Formatter\NormalizerFormatter::detectAndCleanUtf8
+ * @dataProvider providesDetectAndCleanUtf8
+ */
+ public function testDetectAndCleanUtf8($in, $expect)
+ {
+ $formatter = new NormalizerFormatter();
+ $formatter->detectAndCleanUtf8($in);
+ $this->assertSame($expect, $in);
+ }
+
+ public function providesDetectAndCleanUtf8()
+ {
+ $obj = new \stdClass;
+
+ return array(
+ 'null' => array(null, null),
+ 'int' => array(123, 123),
+ 'float' => array(123.45, 123.45),
+ 'bool false' => array(false, false),
+ 'bool true' => array(true, true),
+ 'ascii string' => array('abcdef', 'abcdef'),
+ 'latin9 string' => array("\xB1\x31\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xFF", '±1€ŠšŽžŒœŸÿ'),
+ 'unicode string' => array('¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'),
+ 'empty array' => array(array(), array()),
+ 'array' => array(array('abcdef'), array('abcdef')),
+ 'object' => array($obj, $obj),
+ );
+ }
+
+ /**
+ * @param int $code
+ * @param string $msg
+ * @dataProvider providesHandleJsonErrorFailure
+ */
+ public function testHandleJsonErrorFailure($code, $msg)
+ {
+ $formatter = new NormalizerFormatter();
+ $reflMethod = new \ReflectionMethod($formatter, 'handleJsonError');
+ $reflMethod->setAccessible(true);
+
+ $this->setExpectedException('RuntimeException', $msg);
+ $reflMethod->invoke($formatter, $code, 'faked');
+ }
+
+ public function providesHandleJsonErrorFailure()
+ {
+ return array(
+ 'depth' => array(JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'),
+ 'state' => array(JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'),
+ 'ctrl' => array(JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'),
+ 'default' => array(-1, 'Unknown error'),
+ );
+ }
+
+ public function testExceptionTraceWithArgs()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('Not supported in HHVM since it detects errors differently');
+ }
+
+ // This happens i.e. in React promises or Guzzle streams where stream wrappers are registered
+ // and no file or line are included in the trace because it's treated as internal function
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) {
+ throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
+ });
+
+ try {
+ // This will contain $resource and $wrappedResource as arguments in the trace item
+ $resource = fopen('php://memory', 'rw+');
+ fwrite($resource, 'test_resource');
+ $wrappedResource = new TestFooNorm;
+ $wrappedResource->foo = $resource;
+ // Just do something stupid with a resource/wrapped resource as argument
+ array_keys($wrappedResource);
+ } catch (\Exception $e) {
+ restore_error_handler();
+ }
+
+ $formatter = new NormalizerFormatter();
+ $record = array('context' => array('exception' => $e));
+ $result = $formatter->format($record);
+
+ $this->assertRegExp(
+ '%"resource":"\[resource\] \(stream\)"%',
+ $result['context']['exception']['trace'][0]
+ );
+
+ if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
+ $pattern = '%"wrappedResource":"\[object\] \(Monolog\\\\\\\\Formatter\\\\\\\\TestFooNorm: \)"%';
+ } else {
+ $pattern = '%\\\\"foo\\\\":null%';
+ }
+
+ // Tests that the wrapped resource is ignored while encoding, only works for PHP <= 5.4
+ $this->assertRegExp(
+ $pattern,
+ $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
+{
+ public $foo = 'foo';
+}
+
+class TestBarNorm
+{
+ public function __toString()
+ {
+ return 'bar';
+ }
+}
+
+class TestStreamFoo
+{
+ public $foo;
+ public $resource;
+
+ public function __construct($resource)
+ {
+ $this->resource = $resource;
+ $this->foo = 'BAR';
+ }
+
+ public function __toString()
+ {
+ fseek($this->resource, 0);
+
+ return $this->foo . ' - ' . (string) stream_get_contents($this->resource);
+ }
+}
+
+class TestToStringError
+{
+ public function __toString()
+ {
+ throw new \RuntimeException('Could not convert to string');
+ }
+}
+
+class TestInfoLeak
+{
+ public function __toString()
+ {
+ return 'Sensitive information';
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php
new file mode 100644
index 0000000..b1c8fd4
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+class ScalarFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ private $formatter;
+
+ public function setUp()
+ {
+ $this->formatter = new ScalarFormatter();
+ }
+
+ public function buildTrace(\Exception $e)
+ {
+ $data = array();
+ $trace = $e->getTrace();
+ foreach ($trace as $frame) {
+ if (isset($frame['file'])) {
+ $data[] = $frame['file'].':'.$frame['line'];
+ } else {
+ $data[] = json_encode($frame);
+ }
+ }
+
+ return $data;
+ }
+
+ public function encodeJson($data)
+ {
+ if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
+ return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+ }
+
+ return json_encode($data);
+ }
+
+ public function testFormat()
+ {
+ $exception = new \Exception('foo');
+ $formatted = $this->formatter->format(array(
+ 'foo' => 'string',
+ 'bar' => 1,
+ 'baz' => false,
+ 'bam' => array(1, 2, 3),
+ 'bat' => array('foo' => 'bar'),
+ 'bap' => \DateTime::createFromFormat(\DateTime::ISO8601, '1970-01-01T00:00:00+0000'),
+ 'ban' => $exception,
+ ));
+
+ $this->assertSame(array(
+ 'foo' => 'string',
+ 'bar' => 1,
+ 'baz' => false,
+ 'bam' => $this->encodeJson(array(1, 2, 3)),
+ 'bat' => $this->encodeJson(array('foo' => 'bar')),
+ 'bap' => '1970-01-01 00:00:00',
+ 'ban' => $this->encodeJson(array(
+ 'class' => get_class($exception),
+ 'message' => $exception->getMessage(),
+ 'code' => $exception->getCode(),
+ 'file' => $exception->getFile() . ':' . $exception->getLine(),
+ 'trace' => $this->buildTrace($exception),
+ )),
+ ), $formatted);
+ }
+
+ public function testFormatWithErrorContext()
+ {
+ $context = array('file' => 'foo', 'line' => 1);
+ $formatted = $this->formatter->format(array(
+ 'context' => $context,
+ ));
+
+ $this->assertSame(array(
+ 'context' => $this->encodeJson($context),
+ ), $formatted);
+ }
+
+ public function testFormatWithExceptionContext()
+ {
+ $exception = new \Exception('foo');
+ $formatted = $this->formatter->format(array(
+ 'context' => array(
+ 'exception' => $exception,
+ ),
+ ));
+
+ $this->assertSame(array(
+ 'context' => $this->encodeJson(array(
+ 'exception' => array(
+ 'class' => get_class($exception),
+ 'message' => $exception->getMessage(),
+ 'code' => $exception->getCode(),
+ 'file' => $exception->getFile() . ':' . $exception->getLine(),
+ 'trace' => $this->buildTrace($exception),
+ ),
+ )),
+ ), $formatted);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php b/core/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php
new file mode 100644
index 0000000..52f15a3
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php
@@ -0,0 +1,142 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Logger;
+
+class WildfireFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Monolog\Formatter\WildfireFormatter::format
+ */
+ public function testDefaultFormat()
+ {
+ $wildfire = new WildfireFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('ip' => '127.0.0.1'),
+ 'message' => 'log',
+ );
+
+ $message = $wildfire->format($record);
+
+ $this->assertEquals(
+ '125|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},'
+ .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|',
+ $message
+ );
+ }
+
+ /**
+ * @covers Monolog\Formatter\WildfireFormatter::format
+ */
+ public function testFormatWithFileAndLine()
+ {
+ $wildfire = new WildfireFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('from' => 'logger'),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14),
+ 'message' => 'log',
+ );
+
+ $message = $wildfire->format($record);
+
+ $this->assertEquals(
+ '129|[{"Type":"ERROR","File":"test","Line":14,"Label":"meh"},'
+ .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|',
+ $message
+ );
+ }
+
+ /**
+ * @covers Monolog\Formatter\WildfireFormatter::format
+ */
+ public function testFormatWithoutContext()
+ {
+ $wildfire = new WildfireFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ $message = $wildfire->format($record);
+
+ $this->assertEquals(
+ '58|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},"log"]|',
+ $message
+ );
+ }
+
+ /**
+ * @covers Monolog\Formatter\WildfireFormatter::formatBatch
+ * @expectedException BadMethodCallException
+ */
+ public function testBatchFormatThrowException()
+ {
+ $wildfire = new WildfireFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array(),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ $wildfire->formatBatch(array($record));
+ }
+
+ /**
+ * @covers Monolog\Formatter\WildfireFormatter::format
+ */
+ public function testTableFormat()
+ {
+ $wildfire = new WildfireFormatter();
+ $record = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'table-channel',
+ 'context' => array(
+ WildfireFormatter::TABLE => array(
+ array('col1', 'col2', 'col3'),
+ array('val1', 'val2', 'val3'),
+ array('foo1', 'foo2', 'foo3'),
+ array('bar1', 'bar2', 'bar3'),
+ ),
+ ),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'table-message',
+ );
+
+ $message = $wildfire->format($record);
+
+ $this->assertEquals(
+ '171|[{"Type":"TABLE","File":"","Line":"","Label":"table-channel: table-message"},[["col1","col2","col3"],["val1","val2","val3"],["foo1","foo2","foo3"],["bar1","bar2","bar3"]]]|',
+ $message
+ );
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php
new file mode 100644
index 0000000..568eb9d
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.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\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Processor\WebProcessor;
+
+class AbstractHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\AbstractHandler::__construct
+ * @covers Monolog\Handler\AbstractHandler::getLevel
+ * @covers Monolog\Handler\AbstractHandler::setLevel
+ * @covers Monolog\Handler\AbstractHandler::getBubble
+ * @covers Monolog\Handler\AbstractHandler::setBubble
+ * @covers Monolog\Handler\AbstractHandler::getFormatter
+ * @covers Monolog\Handler\AbstractHandler::setFormatter
+ */
+ public function testConstructAndGetSet()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false));
+ $this->assertEquals(Logger::WARNING, $handler->getLevel());
+ $this->assertEquals(false, $handler->getBubble());
+
+ $handler->setLevel(Logger::ERROR);
+ $handler->setBubble(true);
+ $handler->setFormatter($formatter = new LineFormatter);
+ $this->assertEquals(Logger::ERROR, $handler->getLevel());
+ $this->assertEquals(true, $handler->getBubble());
+ $this->assertSame($formatter, $handler->getFormatter());
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractHandler::handleBatch
+ */
+ public function testHandleBatch()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler');
+ $handler->expects($this->exactly(2))
+ ->method('handle');
+ $handler->handleBatch(array($this->getRecord(), $this->getRecord()));
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractHandler::isHandling
+ */
+ public function testIsHandling()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false));
+ $this->assertTrue($handler->isHandling($this->getRecord()));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG)));
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractHandler::__construct
+ */
+ public function testHandlesPsrStyleLevels()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array('warning', false));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG)));
+ $handler->setLevel('debug');
+ $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG)));
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractHandler::getFormatter
+ * @covers Monolog\Handler\AbstractHandler::getDefaultFormatter
+ */
+ public function testGetFormatterInitializesDefault()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler');
+ $this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter());
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractHandler::pushProcessor
+ * @covers Monolog\Handler\AbstractHandler::popProcessor
+ * @expectedException LogicException
+ */
+ public function testPushPopProcessor()
+ {
+ $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler');
+ $processor1 = new WebProcessor;
+ $processor2 = new WebProcessor;
+
+ $logger->pushProcessor($processor1);
+ $logger->pushProcessor($processor2);
+
+ $this->assertEquals($processor2, $logger->popProcessor());
+ $this->assertEquals($processor1, $logger->popProcessor());
+ $logger->popProcessor();
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractHandler::pushProcessor
+ * @expectedException InvalidArgumentException
+ */
+ public function testPushProcessorWithNonCallable()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler');
+
+ $handler->pushProcessor(new \stdClass());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php
new file mode 100644
index 0000000..24d4f63
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.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;
+use Monolog\Processor\WebProcessor;
+
+class AbstractProcessingHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\AbstractProcessingHandler::handle
+ */
+ public function testHandleLowerLevelMessage()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, true));
+ $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG)));
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractProcessingHandler::handle
+ */
+ public function testHandleBubbling()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, true));
+ $this->assertFalse($handler->handle($this->getRecord()));
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractProcessingHandler::handle
+ */
+ public function testHandleNotBubbling()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, false));
+ $this->assertTrue($handler->handle($this->getRecord()));
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractProcessingHandler::handle
+ */
+ public function testHandleIsFalseWhenNotHandled()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false));
+ $this->assertTrue($handler->handle($this->getRecord()));
+ $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG)));
+ }
+
+ /**
+ * @covers Monolog\Handler\AbstractProcessingHandler::processRecord
+ */
+ public function testProcessRecord()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler');
+ $handler->pushProcessor(new WebProcessor(array(
+ 'REQUEST_URI' => '',
+ 'REQUEST_METHOD' => '',
+ 'REMOTE_ADDR' => '',
+ 'SERVER_NAME' => '',
+ 'UNIQUE_ID' => '',
+ )));
+ $handledRecord = null;
+ $handler->expects($this->once())
+ ->method('write')
+ ->will($this->returnCallback(function ($record) use (&$handledRecord) {
+ $handledRecord = $record;
+ }))
+ ;
+ $handler->handle($this->getRecord());
+ $this->assertEquals(6, count($handledRecord['extra']));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php
new file mode 100644
index 0000000..8e0e723
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php
@@ -0,0 +1,136 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+use PhpAmqpLib\Message\AMQPMessage;
+use PhpAmqpLib\Connection\AMQPConnection;
+
+/**
+ * @covers Monolog\Handler\RotatingFileHandler
+ */
+class AmqpHandlerTest extends TestCase
+{
+ public function testHandleAmqpExt()
+ {
+ if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) {
+ $this->markTestSkipped("amqp-php not installed");
+ }
+
+ if (!class_exists('AMQPChannel')) {
+ $this->markTestSkipped("Please update AMQP to version >= 1.0");
+ }
+
+ $messages = array();
+
+ $exchange = $this->getMock('AMQPExchange', array('publish', 'setName'), array(), '', false);
+ $exchange->expects($this->once())
+ ->method('setName')
+ ->with('log')
+ ;
+ $exchange->expects($this->any())
+ ->method('publish')
+ ->will($this->returnCallback(function ($message, $routing_key, $flags = 0, $attributes = array()) use (&$messages) {
+ $messages[] = array($message, $routing_key, $flags, $attributes);
+ }))
+ ;
+
+ $handler = new AmqpHandler($exchange, 'log');
+
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $expected = array(
+ array(
+ 'message' => 'test',
+ 'context' => array(
+ 'data' => array(),
+ 'foo' => 34,
+ ),
+ 'level' => 300,
+ 'level_name' => 'WARNING',
+ 'channel' => 'test',
+ 'extra' => array(),
+ ),
+ 'warn.test',
+ 0,
+ array(
+ 'delivery_mode' => 2,
+ 'content_type' => 'application/json',
+ ),
+ );
+
+ $handler->handle($record);
+
+ $this->assertCount(1, $messages);
+ $messages[0][0] = json_decode($messages[0][0], true);
+ unset($messages[0][0]['datetime']);
+ $this->assertEquals($expected, $messages[0]);
+ }
+
+ public function testHandlePhpAmqpLib()
+ {
+ if (!class_exists('PhpAmqpLib\Connection\AMQPConnection')) {
+ $this->markTestSkipped("php-amqplib not installed");
+ }
+
+ $messages = array();
+
+ $exchange = $this->getMock('PhpAmqpLib\Channel\AMQPChannel', array('basic_publish', '__destruct'), array(), '', false);
+
+ $exchange->expects($this->any())
+ ->method('basic_publish')
+ ->will($this->returnCallback(function (AMQPMessage $msg, $exchange = "", $routing_key = "", $mandatory = false, $immediate = false, $ticket = null) use (&$messages) {
+ $messages[] = array($msg, $exchange, $routing_key, $mandatory, $immediate, $ticket);
+ }))
+ ;
+
+ $handler = new AmqpHandler($exchange, 'log');
+
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $expected = array(
+ array(
+ 'message' => 'test',
+ 'context' => array(
+ 'data' => array(),
+ 'foo' => 34,
+ ),
+ 'level' => 300,
+ 'level_name' => 'WARNING',
+ 'channel' => 'test',
+ 'extra' => array(),
+ ),
+ 'log',
+ 'warn.test',
+ false,
+ false,
+ null,
+ array(
+ 'delivery_mode' => 2,
+ 'content_type' => 'application/json',
+ ),
+ );
+
+ $handler->handle($record);
+
+ $this->assertCount(1, $messages);
+
+ /* @var $msg AMQPMessage */
+ $msg = $messages[0][0];
+ $messages[0][0] = json_decode($msg->body, true);
+ $messages[0][] = $msg->get_properties();
+ unset($messages[0][0]['datetime']);
+
+ $this->assertEquals($expected, $messages[0]);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php
new file mode 100644
index 0000000..ffe45da
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php
@@ -0,0 +1,130 @@
+
+ *
+ * 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;
+
+/**
+ * @covers Monolog\Handler\BrowserConsoleHandlerTest
+ */
+class BrowserConsoleHandlerTest extends TestCase
+{
+ protected function setUp()
+ {
+ BrowserConsoleHandler::resetStatic();
+ }
+
+ protected function generateScript()
+ {
+ $reflMethod = new \ReflectionMethod('Monolog\Handler\BrowserConsoleHandler', 'generateScript');
+ $reflMethod->setAccessible(true);
+
+ return $reflMethod->invoke(null);
+ }
+
+ public function testStyling()
+ {
+ $handler = new BrowserConsoleHandler();
+ $handler->setFormatter($this->getIdentityFormatter());
+
+ $handler->handle($this->getRecord(Logger::DEBUG, 'foo[[bar]]{color: red}'));
+
+ $expected = <<assertEquals($expected, $this->generateScript());
+ }
+
+ public function testEscaping()
+ {
+ $handler = new BrowserConsoleHandler();
+ $handler->setFormatter($this->getIdentityFormatter());
+
+ $handler->handle($this->getRecord(Logger::DEBUG, "[foo] [[\"bar\n[baz]\"]]{color: red}"));
+
+ $expected = <<assertEquals($expected, $this->generateScript());
+ }
+
+ public function testAutolabel()
+ {
+ $handler = new BrowserConsoleHandler();
+ $handler->setFormatter($this->getIdentityFormatter());
+
+ $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}'));
+ $handler->handle($this->getRecord(Logger::DEBUG, '[[bar]]{macro: autolabel}'));
+ $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}'));
+
+ $expected = <<assertEquals($expected, $this->generateScript());
+ }
+
+ public function testContext()
+ {
+ $handler = new BrowserConsoleHandler();
+ $handler->setFormatter($this->getIdentityFormatter());
+
+ $handler->handle($this->getRecord(Logger::DEBUG, 'test', array('foo' => 'bar')));
+
+ $expected = <<assertEquals($expected, $this->generateScript());
+ }
+
+ public function testConcurrentHandlers()
+ {
+ $handler1 = new BrowserConsoleHandler();
+ $handler1->setFormatter($this->getIdentityFormatter());
+
+ $handler2 = new BrowserConsoleHandler();
+ $handler2->setFormatter($this->getIdentityFormatter());
+
+ $handler1->handle($this->getRecord(Logger::DEBUG, 'test1'));
+ $handler2->handle($this->getRecord(Logger::DEBUG, 'test2'));
+ $handler1->handle($this->getRecord(Logger::DEBUG, 'test3'));
+ $handler2->handle($this->getRecord(Logger::DEBUG, 'test4'));
+
+ $expected = <<assertEquals($expected, $this->generateScript());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php
new file mode 100644
index 0000000..da8b3c3
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php
@@ -0,0 +1,158 @@
+
+ *
+ * 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;
+
+class BufferHandlerTest extends TestCase
+{
+ private $shutdownCheckHandler;
+
+ /**
+ * @covers Monolog\Handler\BufferHandler::__construct
+ * @covers Monolog\Handler\BufferHandler::handle
+ * @covers Monolog\Handler\BufferHandler::close
+ */
+ public function testHandleBuffers()
+ {
+ $test = new TestHandler();
+ $handler = new BufferHandler($test);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $this->assertFalse($test->hasDebugRecords());
+ $this->assertFalse($test->hasInfoRecords());
+ $handler->close();
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertTrue(count($test->getRecords()) === 2);
+ }
+
+ /**
+ * @covers Monolog\Handler\BufferHandler::close
+ * @covers Monolog\Handler\BufferHandler::flush
+ */
+ public function testPropagatesRecordsAtEndOfRequest()
+ {
+ $test = new TestHandler();
+ $handler = new BufferHandler($test);
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $this->shutdownCheckHandler = $test;
+ register_shutdown_function(array($this, 'checkPropagation'));
+ }
+
+ public function checkPropagation()
+ {
+ if (!$this->shutdownCheckHandler->hasWarningRecords() || !$this->shutdownCheckHandler->hasDebugRecords()) {
+ echo '!!! BufferHandlerTest::testPropagatesRecordsAtEndOfRequest failed to verify that the messages have been propagated' . PHP_EOL;
+ exit(1);
+ }
+ }
+
+ /**
+ * @covers Monolog\Handler\BufferHandler::handle
+ */
+ public function testHandleBufferLimit()
+ {
+ $test = new TestHandler();
+ $handler = new BufferHandler($test, 2);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $handler->close();
+ $this->assertTrue($test->hasWarningRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertFalse($test->hasDebugRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\BufferHandler::handle
+ */
+ public function testHandleBufferLimitWithFlushOnOverflow()
+ {
+ $test = new TestHandler();
+ $handler = new BufferHandler($test, 3, Logger::DEBUG, true, true);
+
+ // send two records
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $this->assertFalse($test->hasDebugRecords());
+ $this->assertCount(0, $test->getRecords());
+
+ // overflow
+ $handler->handle($this->getRecord(Logger::INFO));
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertCount(3, $test->getRecords());
+
+ // should buffer again
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertCount(3, $test->getRecords());
+
+ $handler->close();
+ $this->assertCount(5, $test->getRecords());
+ $this->assertTrue($test->hasWarningRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\BufferHandler::handle
+ */
+ public function testHandleLevel()
+ {
+ $test = new TestHandler();
+ $handler = new BufferHandler($test, 0, Logger::INFO);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->close();
+ $this->assertTrue($test->hasWarningRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertFalse($test->hasDebugRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\BufferHandler::flush
+ */
+ public function testFlush()
+ {
+ $test = new TestHandler();
+ $handler = new BufferHandler($test, 0);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $handler->flush();
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertFalse($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\BufferHandler::handle
+ */
+ public function testHandleUsesProcessors()
+ {
+ $test = new TestHandler();
+ $handler = new BufferHandler($test);
+ $handler->pushProcessor(function ($record) {
+ $record['extra']['foo'] = true;
+
+ return $record;
+ });
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $handler->flush();
+ $this->assertTrue($test->hasWarningRecords());
+ $records = $test->getRecords();
+ $this->assertTrue($records[0]['extra']['foo']);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php
new file mode 100644
index 0000000..421cc49
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php
@@ -0,0 +1,156 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+/**
+ * @covers Monolog\Handler\ChromePHPHandler
+ */
+class ChromePHPHandlerTest extends TestCase
+{
+ protected function setUp()
+ {
+ TestChromePHPHandler::resetStatic();
+ $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0';
+ }
+
+ /**
+ * @dataProvider agentsProvider
+ */
+ public function testHeaders($agent)
+ {
+ $_SERVER['HTTP_USER_AGENT'] = $agent;
+
+ $handler = new TestChromePHPHandler();
+ $handler->setFormatter($this->getIdentityFormatter());
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::WARNING));
+
+ $expected = array(
+ 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array(
+ 'version' => ChromePHPHandler::VERSION,
+ 'columns' => array('label', 'log', 'backtrace', 'type'),
+ 'rows' => array(
+ 'test',
+ 'test',
+ ),
+ 'request_uri' => '',
+ )))),
+ );
+
+ $this->assertEquals($expected, $handler->getHeaders());
+ }
+
+ public static function agentsProvider()
+ {
+ return array(
+ array('Monolog Test; Chrome/1.0'),
+ array('Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0'),
+ array('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/56.0.2924.76 Chrome/56.0.2924.76 Safari/537.36'),
+ array('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome Safari/537.36'),
+ );
+ }
+
+ public function testHeadersOverflow()
+ {
+ $handler = new TestChromePHPHandler();
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 150 * 1024)));
+
+ // overflow chrome headers limit
+ $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 100 * 1024)));
+
+ $expected = array(
+ 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array(
+ 'version' => ChromePHPHandler::VERSION,
+ 'columns' => array('label', 'log', 'backtrace', 'type'),
+ 'rows' => array(
+ array(
+ 'test',
+ 'test',
+ 'unknown',
+ 'log',
+ ),
+ array(
+ 'test',
+ str_repeat('a', 150 * 1024),
+ 'unknown',
+ 'warn',
+ ),
+ array(
+ 'monolog',
+ 'Incomplete logs, chrome header size limit reached',
+ 'unknown',
+ 'warn',
+ ),
+ ),
+ 'request_uri' => '',
+ )))),
+ );
+
+ $this->assertEquals($expected, $handler->getHeaders());
+ }
+
+ public function testConcurrentHandlers()
+ {
+ $handler = new TestChromePHPHandler();
+ $handler->setFormatter($this->getIdentityFormatter());
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::WARNING));
+
+ $handler2 = new TestChromePHPHandler();
+ $handler2->setFormatter($this->getIdentityFormatter());
+ $handler2->handle($this->getRecord(Logger::DEBUG));
+ $handler2->handle($this->getRecord(Logger::WARNING));
+
+ $expected = array(
+ 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array(
+ 'version' => ChromePHPHandler::VERSION,
+ 'columns' => array('label', 'log', 'backtrace', 'type'),
+ 'rows' => array(
+ 'test',
+ 'test',
+ 'test',
+ 'test',
+ ),
+ 'request_uri' => '',
+ )))),
+ );
+
+ $this->assertEquals($expected, $handler2->getHeaders());
+ }
+}
+
+class TestChromePHPHandler extends ChromePHPHandler
+{
+ protected $headers = array();
+
+ public static function resetStatic()
+ {
+ self::$initialized = false;
+ self::$overflowed = false;
+ self::$sendHeaders = true;
+ self::$json['rows'] = array();
+ }
+
+ protected function sendHeader($header, $content)
+ {
+ $this->headers[$header] = $content;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php
new file mode 100644
index 0000000..9fc4b38
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.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;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+class CouchDBHandlerTest extends TestCase
+{
+ public function testHandle()
+ {
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $handler = new CouchDBHandler();
+
+ try {
+ $handler->handle($record);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984');
+ }
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php
new file mode 100644
index 0000000..e2aff86
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php
@@ -0,0 +1,165 @@
+
+ *
+ * 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;
+
+class DeduplicationHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\DeduplicationHandler::flush
+ */
+ public function testFlushPassthruIfAllRecordsUnderTrigger()
+ {
+ $test = new TestHandler();
+ @unlink(sys_get_temp_dir().'/monolog_dedup.log');
+ $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
+
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+
+ $handler->flush();
+
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertFalse($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\DeduplicationHandler::flush
+ * @covers Monolog\Handler\DeduplicationHandler::appendRecord
+ */
+ public function testFlushPassthruIfEmptyLog()
+ {
+ $test = new TestHandler();
+ @unlink(sys_get_temp_dir().'/monolog_dedup.log');
+ $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
+
+ $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar'));
+ $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar"));
+
+ $handler->flush();
+
+ $this->assertTrue($test->hasErrorRecords());
+ $this->assertTrue($test->hasCriticalRecords());
+ $this->assertFalse($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\DeduplicationHandler::flush
+ * @covers Monolog\Handler\DeduplicationHandler::appendRecord
+ * @covers Monolog\Handler\DeduplicationHandler::isDuplicate
+ * @depends testFlushPassthruIfEmptyLog
+ */
+ public function testFlushSkipsIfLogExists()
+ {
+ $test = new TestHandler();
+ $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
+
+ $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar'));
+ $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar"));
+
+ $handler->flush();
+
+ $this->assertFalse($test->hasErrorRecords());
+ $this->assertFalse($test->hasCriticalRecords());
+ $this->assertFalse($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\DeduplicationHandler::flush
+ * @covers Monolog\Handler\DeduplicationHandler::appendRecord
+ * @covers Monolog\Handler\DeduplicationHandler::isDuplicate
+ * @depends testFlushPassthruIfEmptyLog
+ */
+ public function testFlushPassthruIfLogTooOld()
+ {
+ $test = new TestHandler();
+ $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
+
+ $record = $this->getRecord(Logger::ERROR);
+ $record['datetime']->modify('+62seconds');
+ $handler->handle($record);
+ $record = $this->getRecord(Logger::CRITICAL);
+ $record['datetime']->modify('+62seconds');
+ $handler->handle($record);
+
+ $handler->flush();
+
+ $this->assertTrue($test->hasErrorRecords());
+ $this->assertTrue($test->hasCriticalRecords());
+ $this->assertFalse($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\DeduplicationHandler::flush
+ * @covers Monolog\Handler\DeduplicationHandler::appendRecord
+ * @covers Monolog\Handler\DeduplicationHandler::isDuplicate
+ * @covers Monolog\Handler\DeduplicationHandler::collectLogs
+ */
+ public function testGcOldLogs()
+ {
+ $test = new TestHandler();
+ @unlink(sys_get_temp_dir().'/monolog_dedup.log');
+ $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
+
+ // handle two records from yesterday, and one recent
+ $record = $this->getRecord(Logger::ERROR);
+ $record['datetime']->modify('-1day -10seconds');
+ $handler->handle($record);
+ $record2 = $this->getRecord(Logger::CRITICAL);
+ $record2['datetime']->modify('-1day -10seconds');
+ $handler->handle($record2);
+ $record3 = $this->getRecord(Logger::CRITICAL);
+ $record3['datetime']->modify('-30seconds');
+ $handler->handle($record3);
+
+ // log is written as none of them are duplicate
+ $handler->flush();
+ $this->assertSame(
+ $record['datetime']->getTimestamp() . ":ERROR:test\n" .
+ $record2['datetime']->getTimestamp() . ":CRITICAL:test\n" .
+ $record3['datetime']->getTimestamp() . ":CRITICAL:test\n",
+ file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log')
+ );
+ $this->assertTrue($test->hasErrorRecords());
+ $this->assertTrue($test->hasCriticalRecords());
+ $this->assertFalse($test->hasWarningRecords());
+
+ // clear test handler
+ $test->clear();
+ $this->assertFalse($test->hasErrorRecords());
+ $this->assertFalse($test->hasCriticalRecords());
+
+ // log new records, duplicate log gets GC'd at the end of this flush call
+ $handler->handle($record = $this->getRecord(Logger::ERROR));
+ $handler->handle($record2 = $this->getRecord(Logger::CRITICAL));
+ $handler->flush();
+
+ // log should now contain the new errors and the previous one that was recent enough
+ $this->assertSame(
+ $record3['datetime']->getTimestamp() . ":CRITICAL:test\n" .
+ $record['datetime']->getTimestamp() . ":ERROR:test\n" .
+ $record2['datetime']->getTimestamp() . ":CRITICAL:test\n",
+ file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log')
+ );
+ $this->assertTrue($test->hasErrorRecords());
+ $this->assertTrue($test->hasCriticalRecords());
+ $this->assertFalse($test->hasWarningRecords());
+ }
+
+ public static function tearDownAfterClass()
+ {
+ @unlink(sys_get_temp_dir().'/monolog_dedup.log');
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php
new file mode 100644
index 0000000..d67da90
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php
@@ -0,0 +1,52 @@
+
+ *
+ * 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;
+
+class DoctrineCouchDBHandlerTest extends TestCase
+{
+ protected function setup()
+ {
+ if (!class_exists('Doctrine\CouchDB\CouchDBClient')) {
+ $this->markTestSkipped('The "doctrine/couchdb" package is not installed');
+ }
+ }
+
+ public function testHandle()
+ {
+ $client = $this->getMockBuilder('Doctrine\\CouchDB\\CouchDBClient')
+ ->setMethods(array('postDocument'))
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $expected = array(
+ 'message' => 'test',
+ 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34),
+ 'level' => Logger::WARNING,
+ 'level_name' => 'WARNING',
+ 'channel' => 'test',
+ 'datetime' => $record['datetime']->format('Y-m-d H:i:s'),
+ 'extra' => array(),
+ );
+
+ $client->expects($this->once())
+ ->method('postDocument')
+ ->with($expected);
+
+ $handler = new DoctrineCouchDBHandler($client);
+ $handler->handle($record);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php
new file mode 100644
index 0000000..2e6c348
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php
@@ -0,0 +1,82 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+
+class DynamoDbHandlerTest extends TestCase
+{
+ private $client;
+
+ public function setUp()
+ {
+ if (!class_exists('Aws\DynamoDb\DynamoDbClient')) {
+ $this->markTestSkipped('aws/aws-sdk-php not installed');
+ }
+
+ $this->client = $this->getMockBuilder('Aws\DynamoDb\DynamoDbClient')
+ ->setMethods(array('formatAttributes', '__call'))
+ ->disableOriginalConstructor()->getMock();
+ }
+
+ public function testConstruct()
+ {
+ $this->assertInstanceOf('Monolog\Handler\DynamoDbHandler', new DynamoDbHandler($this->client, 'foo'));
+ }
+
+ public function testInterface()
+ {
+ $this->assertInstanceOf('Monolog\Handler\HandlerInterface', new DynamoDbHandler($this->client, 'foo'));
+ }
+
+ public function testGetFormatter()
+ {
+ $handler = new DynamoDbHandler($this->client, 'foo');
+ $this->assertInstanceOf('Monolog\Formatter\ScalarFormatter', $handler->getFormatter());
+ }
+
+ public function testHandle()
+ {
+ $record = $this->getRecord();
+ $formatter = $this->getMock('Monolog\Formatter\FormatterInterface');
+ $formatted = array('foo' => 1, 'bar' => 2);
+ $handler = new DynamoDbHandler($this->client, 'foo');
+ $handler->setFormatter($formatter);
+
+ $isV3 = defined('Aws\Sdk::VERSION') && version_compare(\Aws\Sdk::VERSION, '3.0', '>=');
+ if ($isV3) {
+ $expFormatted = array('foo' => array('N' => 1), 'bar' => array('N' => 2));
+ } else {
+ $expFormatted = $formatted;
+ }
+
+ $formatter
+ ->expects($this->once())
+ ->method('format')
+ ->with($record)
+ ->will($this->returnValue($formatted));
+ $this->client
+ ->expects($isV3 ? $this->never() : $this->once())
+ ->method('formatAttributes')
+ ->with($this->isType('array'))
+ ->will($this->returnValue($formatted));
+ $this->client
+ ->expects($this->once())
+ ->method('__call')
+ ->with('putItem', array(array(
+ 'TableName' => 'foo',
+ 'Item' => $expFormatted,
+ )));
+
+ $handler->handle($record);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php
new file mode 100644
index 0000000..1687074
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php
@@ -0,0 +1,239 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\ElasticaFormatter;
+use Monolog\Formatter\NormalizerFormatter;
+use Monolog\TestCase;
+use Monolog\Logger;
+use Elastica\Client;
+use Elastica\Request;
+use Elastica\Response;
+
+class ElasticSearchHandlerTest extends TestCase
+{
+ /**
+ * @var Client mock
+ */
+ protected $client;
+
+ /**
+ * @var array Default handler options
+ */
+ protected $options = array(
+ 'index' => 'my_index',
+ 'type' => 'doc_type',
+ );
+
+ public function setUp()
+ {
+ // Elastica lib required
+ if (!class_exists("Elastica\Client")) {
+ $this->markTestSkipped("ruflin/elastica not installed");
+ }
+
+ // base mock Elastica Client object
+ $this->client = $this->getMockBuilder('Elastica\Client')
+ ->setMethods(array('addDocuments'))
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ /**
+ * @covers Monolog\Handler\ElasticSearchHandler::write
+ * @covers Monolog\Handler\ElasticSearchHandler::handleBatch
+ * @covers Monolog\Handler\ElasticSearchHandler::bulkSend
+ * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter
+ */
+ public function testHandle()
+ {
+ // log message
+ $msg = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ // format expected result
+ $formatter = new ElasticaFormatter($this->options['index'], $this->options['type']);
+ $expected = array($formatter->format($msg));
+
+ // setup ES client mock
+ $this->client->expects($this->any())
+ ->method('addDocuments')
+ ->with($expected);
+
+ // perform tests
+ $handler = new ElasticSearchHandler($this->client, $this->options);
+ $handler->handle($msg);
+ $handler->handleBatch(array($msg));
+ }
+
+ /**
+ * @covers Monolog\Handler\ElasticSearchHandler::setFormatter
+ */
+ public function testSetFormatter()
+ {
+ $handler = new ElasticSearchHandler($this->client);
+ $formatter = new ElasticaFormatter('index_new', 'type_new');
+ $handler->setFormatter($formatter);
+ $this->assertInstanceOf('Monolog\Formatter\ElasticaFormatter', $handler->getFormatter());
+ $this->assertEquals('index_new', $handler->getFormatter()->getIndex());
+ $this->assertEquals('type_new', $handler->getFormatter()->getType());
+ }
+
+ /**
+ * @covers Monolog\Handler\ElasticSearchHandler::setFormatter
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage ElasticSearchHandler is only compatible with ElasticaFormatter
+ */
+ public function testSetFormatterInvalid()
+ {
+ $handler = new ElasticSearchHandler($this->client);
+ $formatter = new NormalizerFormatter();
+ $handler->setFormatter($formatter);
+ }
+
+ /**
+ * @covers Monolog\Handler\ElasticSearchHandler::__construct
+ * @covers Monolog\Handler\ElasticSearchHandler::getOptions
+ */
+ public function testOptions()
+ {
+ $expected = array(
+ 'index' => $this->options['index'],
+ 'type' => $this->options['type'],
+ 'ignore_error' => false,
+ );
+ $handler = new ElasticSearchHandler($this->client, $this->options);
+ $this->assertEquals($expected, $handler->getOptions());
+ }
+
+ /**
+ * @covers Monolog\Handler\ElasticSearchHandler::bulkSend
+ * @dataProvider providerTestConnectionErrors
+ */
+ public function testConnectionErrors($ignore, $expectedError)
+ {
+ $clientOpts = array('host' => '127.0.0.1', 'port' => 1);
+ $client = new Client($clientOpts);
+ $handlerOpts = array('ignore_error' => $ignore);
+ $handler = new ElasticSearchHandler($client, $handlerOpts);
+
+ if ($expectedError) {
+ $this->setExpectedException($expectedError[0], $expectedError[1]);
+ $handler->handle($this->getRecord());
+ } else {
+ $this->assertFalse($handler->handle($this->getRecord()));
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function providerTestConnectionErrors()
+ {
+ return array(
+ array(false, array('RuntimeException', 'Error sending messages to Elasticsearch')),
+ array(true, false),
+ );
+ }
+
+ /**
+ * Integration test using localhost Elastic Search server
+ *
+ * @covers Monolog\Handler\ElasticSearchHandler::__construct
+ * @covers Monolog\Handler\ElasticSearchHandler::handleBatch
+ * @covers Monolog\Handler\ElasticSearchHandler::bulkSend
+ * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter
+ */
+ public function testHandleIntegration()
+ {
+ $msg = array(
+ 'level' => Logger::ERROR,
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass),
+ 'datetime' => new \DateTime("@0"),
+ 'extra' => array(),
+ 'message' => 'log',
+ );
+
+ $expected = $msg;
+ $expected['datetime'] = $msg['datetime']->format(\DateTime::ISO8601);
+ $expected['context'] = array(
+ 'class' => '[object] (stdClass: {})',
+ 'foo' => 7,
+ 0 => 'bar',
+ );
+
+ $client = new Client();
+ $handler = new ElasticSearchHandler($client, $this->options);
+ try {
+ $handler->handleBatch(array($msg));
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped("Cannot connect to Elastic Search server on localhost");
+ }
+
+ // check document id from ES server response
+ $documentId = $this->getCreatedDocId($client->getLastResponse());
+ $this->assertNotEmpty($documentId, 'No elastic document id received');
+
+ // retrieve document source from ES and validate
+ $document = $this->getDocSourceFromElastic(
+ $client,
+ $this->options['index'],
+ $this->options['type'],
+ $documentId
+ );
+ $this->assertEquals($expected, $document);
+
+ // remove test index from ES
+ $client->request("/{$this->options['index']}", Request::DELETE);
+ }
+
+ /**
+ * Return last created document id from ES response
+ * @param Response $response Elastica Response object
+ * @return string|null
+ */
+ protected function getCreatedDocId(Response $response)
+ {
+ $data = $response->getData();
+ if (!empty($data['items'][0]['create']['_id'])) {
+ return $data['items'][0]['create']['_id'];
+ }
+ }
+
+ /**
+ * Retrieve document by id from Elasticsearch
+ * @param Client $client Elastica client
+ * @param string $index
+ * @param string $type
+ * @param string $documentId
+ * @return array
+ */
+ protected function getDocSourceFromElastic(Client $client, $index, $type, $documentId)
+ {
+ $resp = $client->request("/{$index}/{$type}/{$documentId}", Request::GET);
+ $data = $resp->getData();
+ if (!empty($data['_source'])) {
+ return $data['_source'];
+ }
+
+ return array();
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php
new file mode 100644
index 0000000..99785cb
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php
@@ -0,0 +1,66 @@
+
+ *
+ * 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;
+use Monolog\Formatter\LineFormatter;
+
+function error_log()
+{
+ $GLOBALS['error_log'][] = func_get_args();
+}
+
+class ErrorLogHandlerTest extends TestCase
+{
+ protected function setUp()
+ {
+ $GLOBALS['error_log'] = array();
+ }
+
+ /**
+ * @covers Monolog\Handler\ErrorLogHandler::__construct
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage The given message type "42" is not supported
+ */
+ public function testShouldNotAcceptAnInvalidTypeOnContructor()
+ {
+ new ErrorLogHandler(42);
+ }
+
+ /**
+ * @covers Monolog\Handler\ErrorLogHandler::write
+ */
+ public function testShouldLogMessagesUsingErrorLogFuncion()
+ {
+ $type = ErrorLogHandler::OPERATING_SYSTEM;
+ $handler = new ErrorLogHandler($type);
+ $handler->setFormatter(new LineFormatter('%channel%.%level_name%: %message% %context% %extra%', null, true));
+ $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz"));
+
+ $this->assertSame("test.ERROR: Foo\nBar\r\n\r\nBaz [] []", $GLOBALS['error_log'][0][0]);
+ $this->assertSame($GLOBALS['error_log'][0][1], $type);
+
+ $handler = new ErrorLogHandler($type, Logger::DEBUG, true, true);
+ $handler->setFormatter(new LineFormatter(null, null, true));
+ $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz"));
+
+ $this->assertStringMatchesFormat('[%s] test.ERROR: Foo', $GLOBALS['error_log'][1][0]);
+ $this->assertSame($GLOBALS['error_log'][1][1], $type);
+
+ $this->assertStringMatchesFormat('Bar', $GLOBALS['error_log'][2][0]);
+ $this->assertSame($GLOBALS['error_log'][2][1], $type);
+
+ $this->assertStringMatchesFormat('Baz [] []', $GLOBALS['error_log'][3][0]);
+ $this->assertSame($GLOBALS['error_log'][3][1], $type);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php
new file mode 100644
index 0000000..31b7686
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php
@@ -0,0 +1,170 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+use Monolog\TestCase;
+
+class FilterHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\FilterHandler::isHandling
+ */
+ public function testIsHandling()
+ {
+ $test = new TestHandler();
+ $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE);
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG)));
+ $this->assertTrue($handler->isHandling($this->getRecord(Logger::INFO)));
+ $this->assertTrue($handler->isHandling($this->getRecord(Logger::NOTICE)));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::WARNING)));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::ERROR)));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::CRITICAL)));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::ALERT)));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::EMERGENCY)));
+ }
+
+ /**
+ * @covers Monolog\Handler\FilterHandler::handle
+ * @covers Monolog\Handler\FilterHandler::setAcceptedLevels
+ * @covers Monolog\Handler\FilterHandler::isHandling
+ */
+ public function testHandleProcessOnlyNeededLevels()
+ {
+ $test = new TestHandler();
+ $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE);
+
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $this->assertFalse($test->hasDebugRecords());
+
+ $handler->handle($this->getRecord(Logger::INFO));
+ $this->assertTrue($test->hasInfoRecords());
+ $handler->handle($this->getRecord(Logger::NOTICE));
+ $this->assertTrue($test->hasNoticeRecords());
+
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertFalse($test->hasWarningRecords());
+ $handler->handle($this->getRecord(Logger::ERROR));
+ $this->assertFalse($test->hasErrorRecords());
+ $handler->handle($this->getRecord(Logger::CRITICAL));
+ $this->assertFalse($test->hasCriticalRecords());
+ $handler->handle($this->getRecord(Logger::ALERT));
+ $this->assertFalse($test->hasAlertRecords());
+ $handler->handle($this->getRecord(Logger::EMERGENCY));
+ $this->assertFalse($test->hasEmergencyRecords());
+
+ $test = new TestHandler();
+ $handler = new FilterHandler($test, array(Logger::INFO, Logger::ERROR));
+
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $this->assertFalse($test->hasDebugRecords());
+ $handler->handle($this->getRecord(Logger::INFO));
+ $this->assertTrue($test->hasInfoRecords());
+ $handler->handle($this->getRecord(Logger::NOTICE));
+ $this->assertFalse($test->hasNoticeRecords());
+ $handler->handle($this->getRecord(Logger::ERROR));
+ $this->assertTrue($test->hasErrorRecords());
+ $handler->handle($this->getRecord(Logger::CRITICAL));
+ $this->assertFalse($test->hasCriticalRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FilterHandler::setAcceptedLevels
+ * @covers Monolog\Handler\FilterHandler::getAcceptedLevels
+ */
+ public function testAcceptedLevelApi()
+ {
+ $test = new TestHandler();
+ $handler = new FilterHandler($test);
+
+ $levels = array(Logger::INFO, Logger::ERROR);
+ $handler->setAcceptedLevels($levels);
+ $this->assertSame($levels, $handler->getAcceptedLevels());
+
+ $handler->setAcceptedLevels(array('info', 'error'));
+ $this->assertSame($levels, $handler->getAcceptedLevels());
+
+ $levels = array(Logger::CRITICAL, Logger::ALERT, Logger::EMERGENCY);
+ $handler->setAcceptedLevels(Logger::CRITICAL, Logger::EMERGENCY);
+ $this->assertSame($levels, $handler->getAcceptedLevels());
+
+ $handler->setAcceptedLevels('critical', 'emergency');
+ $this->assertSame($levels, $handler->getAcceptedLevels());
+ }
+
+ /**
+ * @covers Monolog\Handler\FilterHandler::handle
+ */
+ public function testHandleUsesProcessors()
+ {
+ $test = new TestHandler();
+ $handler = new FilterHandler($test, Logger::DEBUG, Logger::EMERGENCY);
+ $handler->pushProcessor(
+ function ($record) {
+ $record['extra']['foo'] = true;
+
+ return $record;
+ }
+ );
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasWarningRecords());
+ $records = $test->getRecords();
+ $this->assertTrue($records[0]['extra']['foo']);
+ }
+
+ /**
+ * @covers Monolog\Handler\FilterHandler::handle
+ */
+ public function testHandleRespectsBubble()
+ {
+ $test = new TestHandler();
+
+ $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, false);
+ $this->assertTrue($handler->handle($this->getRecord(Logger::INFO)));
+ $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING)));
+
+ $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, true);
+ $this->assertFalse($handler->handle($this->getRecord(Logger::INFO)));
+ $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING)));
+ }
+
+ /**
+ * @covers Monolog\Handler\FilterHandler::handle
+ */
+ public function testHandleWithCallback()
+ {
+ $test = new TestHandler();
+ $handler = new FilterHandler(
+ function ($record, $handler) use ($test) {
+ return $test;
+ }, Logger::INFO, Logger::NOTICE, false
+ );
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $this->assertFalse($test->hasDebugRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FilterHandler::handle
+ * @expectedException \RuntimeException
+ */
+ public function testHandleWithBadCallbackThrowsException()
+ {
+ $handler = new FilterHandler(
+ function ($record, $handler) {
+ return 'foo';
+ }
+ );
+ $handler->handle($this->getRecord(Logger::WARNING));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php
new file mode 100644
index 0000000..0ec3653
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php
@@ -0,0 +1,279 @@
+
+ *
+ * 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;
+use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
+use Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy;
+use Psr\Log\LogLevel;
+
+class FingersCrossedHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::__construct
+ * @covers Monolog\Handler\FingersCrossedHandler::handle
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ */
+ public function testHandleBuffers()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $this->assertFalse($test->hasDebugRecords());
+ $this->assertFalse($test->hasInfoRecords());
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $handler->close();
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertTrue(count($test->getRecords()) === 3);
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::handle
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ */
+ public function testHandleStopsBufferingAfterTrigger()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test);
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->close();
+ $this->assertTrue($test->hasWarningRecords());
+ $this->assertTrue($test->hasDebugRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::handle
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ * @covers Monolog\Handler\FingersCrossedHandler::reset
+ */
+ public function testHandleResetBufferingAfterReset()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test);
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->reset();
+ $handler->handle($this->getRecord(Logger::INFO));
+ $handler->close();
+ $this->assertTrue($test->hasWarningRecords());
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertFalse($test->hasInfoRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::handle
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ */
+ public function testHandleResetBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $handler->close();
+ $this->assertTrue($test->hasWarningRecords());
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertFalse($test->hasInfoRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::handle
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ */
+ public function testHandleBufferLimit()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, Logger::WARNING, 2);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasWarningRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertFalse($test->hasDebugRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::handle
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ */
+ public function testHandleWithCallback()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler(function ($record, $handler) use ($test) {
+ return $test;
+ });
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $this->assertFalse($test->hasDebugRecords());
+ $this->assertFalse($test->hasInfoRecords());
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertTrue(count($test->getRecords()) === 3);
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::handle
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ * @expectedException RuntimeException
+ */
+ public function testHandleWithBadCallbackThrowsException()
+ {
+ $handler = new FingersCrossedHandler(function ($record, $handler) {
+ return 'foo';
+ });
+ $handler->handle($this->getRecord(Logger::WARNING));
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::isHandling
+ */
+ public function testIsHandlingAlways()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, Logger::ERROR);
+ $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG)));
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::__construct
+ * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct
+ * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated
+ */
+ public function testErrorLevelActivationStrategy()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $this->assertFalse($test->hasDebugRecords());
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertTrue($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::__construct
+ * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct
+ * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated
+ */
+ public function testErrorLevelActivationStrategyWithPsrLevel()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning'));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $this->assertFalse($test->hasDebugRecords());
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertTrue($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::__construct
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ */
+ public function testOverrideActivationStrategy()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning'));
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $this->assertFalse($test->hasDebugRecords());
+ $handler->activate();
+ $this->assertTrue($test->hasDebugRecords());
+ $handler->handle($this->getRecord(Logger::INFO));
+ $this->assertTrue($test->hasInfoRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct
+ * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated
+ */
+ public function testChannelLevelActivationStrategy()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Logger::ERROR, array('othertest' => Logger::DEBUG)));
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertFalse($test->hasWarningRecords());
+ $record = $this->getRecord(Logger::DEBUG);
+ $record['channel'] = 'othertest';
+ $handler->handle($record);
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertTrue($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct
+ * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated
+ */
+ public function testChannelLevelActivationStrategyWithPsrLevels()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy('error', array('othertest' => 'debug')));
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertFalse($test->hasWarningRecords());
+ $record = $this->getRecord(Logger::DEBUG);
+ $record['channel'] = 'othertest';
+ $handler->handle($record);
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertTrue($test->hasWarningRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::handle
+ * @covers Monolog\Handler\FingersCrossedHandler::activate
+ */
+ public function testHandleUsesProcessors()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, Logger::INFO);
+ $handler->pushProcessor(function ($record) {
+ $record['extra']['foo'] = true;
+
+ return $record;
+ });
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasWarningRecords());
+ $records = $test->getRecords();
+ $this->assertTrue($records[0]['extra']['foo']);
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::close
+ */
+ public function testPassthruOnClose()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, Logger::INFO);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $handler->close();
+ $this->assertFalse($test->hasDebugRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\FingersCrossedHandler::close
+ */
+ public function testPsrLevelPassthruOnClose()
+ {
+ $test = new TestHandler();
+ $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, LogLevel::INFO);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ $handler->close();
+ $this->assertFalse($test->hasDebugRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php
new file mode 100644
index 0000000..7a404e6
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php
@@ -0,0 +1,96 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+/**
+ * @covers Monolog\Handler\FirePHPHandler
+ */
+class FirePHPHandlerTest extends TestCase
+{
+ public function setUp()
+ {
+ TestFirePHPHandler::resetStatic();
+ $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0';
+ }
+
+ public function testHeaders()
+ {
+ $handler = new TestFirePHPHandler;
+ $handler->setFormatter($this->getIdentityFormatter());
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::WARNING));
+
+ $expected = array(
+ 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2',
+ 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1',
+ 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3',
+ 'X-Wf-1-1-1-1' => 'test',
+ 'X-Wf-1-1-1-2' => 'test',
+ );
+
+ $this->assertEquals($expected, $handler->getHeaders());
+ }
+
+ public function testConcurrentHandlers()
+ {
+ $handler = new TestFirePHPHandler;
+ $handler->setFormatter($this->getIdentityFormatter());
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::WARNING));
+
+ $handler2 = new TestFirePHPHandler;
+ $handler2->setFormatter($this->getIdentityFormatter());
+ $handler2->handle($this->getRecord(Logger::DEBUG));
+ $handler2->handle($this->getRecord(Logger::WARNING));
+
+ $expected = array(
+ 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2',
+ 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1',
+ 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3',
+ 'X-Wf-1-1-1-1' => 'test',
+ 'X-Wf-1-1-1-2' => 'test',
+ );
+
+ $expected2 = array(
+ 'X-Wf-1-1-1-3' => 'test',
+ 'X-Wf-1-1-1-4' => 'test',
+ );
+
+ $this->assertEquals($expected, $handler->getHeaders());
+ $this->assertEquals($expected2, $handler2->getHeaders());
+ }
+}
+
+class TestFirePHPHandler extends FirePHPHandler
+{
+ protected $headers = array();
+
+ public static function resetStatic()
+ {
+ self::$initialized = false;
+ self::$sendHeaders = true;
+ self::$messageIndex = 1;
+ }
+
+ protected function sendHeader($header, $content)
+ {
+ $this->headers[$header] = $content;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep b/core/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php
new file mode 100644
index 0000000..91cdd31
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Logger;
+use Monolog\TestCase;
+
+/**
+ * @coversDefaultClass \Monolog\Handler\FleepHookHandler
+ */
+class FleepHookHandlerTest extends TestCase
+{
+ /**
+ * Default token to use in tests
+ */
+ const TOKEN = '123abc';
+
+ /**
+ * @var FleepHookHandler
+ */
+ private $handler;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ if (!extension_loaded('openssl')) {
+ $this->markTestSkipped('This test requires openssl extension to run');
+ }
+
+ // Create instances of the handler and logger for convenience
+ $this->handler = new FleepHookHandler(self::TOKEN);
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testConstructorSetsExpectedDefaults()
+ {
+ $this->assertEquals(Logger::DEBUG, $this->handler->getLevel());
+ $this->assertEquals(true, $this->handler->getBubble());
+ }
+
+ /**
+ * @covers ::getDefaultFormatter
+ */
+ public function testHandlerUsesLineFormatterWhichIgnoresEmptyArrays()
+ {
+ $record = array(
+ 'message' => 'msg',
+ 'context' => array(),
+ 'level' => Logger::DEBUG,
+ 'level_name' => Logger::getLevelName(Logger::DEBUG),
+ 'channel' => 'channel',
+ 'datetime' => new \DateTime(),
+ 'extra' => array(),
+ );
+
+ $expectedFormatter = new LineFormatter(null, null, true, true);
+ $expected = $expectedFormatter->format($record);
+
+ $handlerFormatter = $this->handler->getFormatter();
+ $actual = $handlerFormatter->format($record);
+
+ $this->assertEquals($expected, $actual, 'Empty context and extra arrays should not be rendered');
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testConnectionStringisConstructedCorrectly()
+ {
+ $this->assertEquals('ssl://' . FleepHookHandler::FLEEP_HOST . ':443', $this->handler->getConnectionString());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php
new file mode 100644
index 0000000..4b120d5
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FlowdockFormatter;
+use Monolog\TestCase;
+use Monolog\Logger;
+
+/**
+ * @author Dominik Liebler
+ * @see https://www.hipchat.com/docs/api
+ */
+class FlowdockHandlerTest extends TestCase
+{
+ /**
+ * @var resource
+ */
+ private $res;
+
+ /**
+ * @var FlowdockHandler
+ */
+ private $handler;
+
+ public function setUp()
+ {
+ if (!extension_loaded('openssl')) {
+ $this->markTestSkipped('This test requires openssl to run');
+ }
+ }
+
+ public function testWriteHeader()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/POST \/v1\/messages\/team_inbox\/.* HTTP\/1.1\\r\\nHost: api.flowdock.com\\r\\nContent-Type: application\/json\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+
+ return $content;
+ }
+
+ /**
+ * @depends testWriteHeader
+ */
+ public function testWriteContent($content)
+ {
+ $this->assertRegexp('/"source":"test_source"/', $content);
+ $this->assertRegexp('/"from_address":"source@test\.com"/', $content);
+ }
+
+ private function createHandler($token = 'myToken')
+ {
+ $constructorArgs = array($token, Logger::DEBUG);
+ $this->res = fopen('php://memory', 'a');
+ $this->handler = $this->getMock(
+ '\Monolog\Handler\FlowdockHandler',
+ array('fsockopen', 'streamSetTimeout', 'closeSocket'),
+ $constructorArgs
+ );
+
+ $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->res));
+ $this->handler->expects($this->any())
+ ->method('streamSetTimeout')
+ ->will($this->returnValue(true));
+ $this->handler->expects($this->any())
+ ->method('closeSocket')
+ ->will($this->returnValue(true));
+
+ $this->handler->setFormatter(new FlowdockFormatter('test_source', 'source@test.com'));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php
new file mode 100644
index 0000000..9d007b1
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Gelf\Message;
+use Monolog\TestCase;
+use Monolog\Logger;
+use Monolog\Formatter\GelfMessageFormatter;
+
+class GelfHandlerLegacyTest extends TestCase
+{
+ public function setUp()
+ {
+ if (!class_exists('Gelf\MessagePublisher') || !class_exists('Gelf\Message')) {
+ $this->markTestSkipped("mlehner/gelf-php not installed");
+ }
+
+ require_once __DIR__ . '/GelfMockMessagePublisher.php';
+ }
+
+ /**
+ * @covers Monolog\Handler\GelfHandler::__construct
+ */
+ public function testConstruct()
+ {
+ $handler = new GelfHandler($this->getMessagePublisher());
+ $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler);
+ }
+
+ protected function getHandler($messagePublisher)
+ {
+ $handler = new GelfHandler($messagePublisher);
+
+ return $handler;
+ }
+
+ protected function getMessagePublisher()
+ {
+ return new GelfMockMessagePublisher('localhost');
+ }
+
+ public function testDebug()
+ {
+ $messagePublisher = $this->getMessagePublisher();
+ $handler = $this->getHandler($messagePublisher);
+
+ $record = $this->getRecord(Logger::DEBUG, "A test debug message");
+ $handler->handle($record);
+
+ $this->assertEquals(7, $messagePublisher->lastMessage->getLevel());
+ $this->assertEquals('test', $messagePublisher->lastMessage->getFacility());
+ $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage());
+ $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage());
+ }
+
+ public function testWarning()
+ {
+ $messagePublisher = $this->getMessagePublisher();
+ $handler = $this->getHandler($messagePublisher);
+
+ $record = $this->getRecord(Logger::WARNING, "A test warning message");
+ $handler->handle($record);
+
+ $this->assertEquals(4, $messagePublisher->lastMessage->getLevel());
+ $this->assertEquals('test', $messagePublisher->lastMessage->getFacility());
+ $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage());
+ $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage());
+ }
+
+ public function testInjectedGelfMessageFormatter()
+ {
+ $messagePublisher = $this->getMessagePublisher();
+ $handler = $this->getHandler($messagePublisher);
+
+ $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX'));
+
+ $record = $this->getRecord(Logger::WARNING, "A test warning message");
+ $record['extra']['blarg'] = 'yep';
+ $record['context']['from'] = 'logger';
+ $handler->handle($record);
+
+ $this->assertEquals('mysystem', $messagePublisher->lastMessage->getHost());
+ $this->assertArrayHasKey('_EXTblarg', $messagePublisher->lastMessage->toArray());
+ $this->assertArrayHasKey('_CTXfrom', $messagePublisher->lastMessage->toArray());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php
new file mode 100644
index 0000000..8cdd64f
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php
@@ -0,0 +1,117 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Gelf\Message;
+use Monolog\TestCase;
+use Monolog\Logger;
+use Monolog\Formatter\GelfMessageFormatter;
+
+class GelfHandlerTest extends TestCase
+{
+ public function setUp()
+ {
+ if (!class_exists('Gelf\Publisher') || !class_exists('Gelf\Message')) {
+ $this->markTestSkipped("graylog2/gelf-php not installed");
+ }
+ }
+
+ /**
+ * @covers Monolog\Handler\GelfHandler::__construct
+ */
+ public function testConstruct()
+ {
+ $handler = new GelfHandler($this->getMessagePublisher());
+ $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler);
+ }
+
+ protected function getHandler($messagePublisher)
+ {
+ $handler = new GelfHandler($messagePublisher);
+
+ return $handler;
+ }
+
+ protected function getMessagePublisher()
+ {
+ return $this->getMock('Gelf\Publisher', array('publish'), array(), '', false);
+ }
+
+ public function testDebug()
+ {
+ $record = $this->getRecord(Logger::DEBUG, "A test debug message");
+ $expectedMessage = new Message();
+ $expectedMessage
+ ->setLevel(7)
+ ->setFacility("test")
+ ->setShortMessage($record['message'])
+ ->setTimestamp($record['datetime'])
+ ;
+
+ $messagePublisher = $this->getMessagePublisher();
+ $messagePublisher->expects($this->once())
+ ->method('publish')
+ ->with($expectedMessage);
+
+ $handler = $this->getHandler($messagePublisher);
+
+ $handler->handle($record);
+ }
+
+ public function testWarning()
+ {
+ $record = $this->getRecord(Logger::WARNING, "A test warning message");
+ $expectedMessage = new Message();
+ $expectedMessage
+ ->setLevel(4)
+ ->setFacility("test")
+ ->setShortMessage($record['message'])
+ ->setTimestamp($record['datetime'])
+ ;
+
+ $messagePublisher = $this->getMessagePublisher();
+ $messagePublisher->expects($this->once())
+ ->method('publish')
+ ->with($expectedMessage);
+
+ $handler = $this->getHandler($messagePublisher);
+
+ $handler->handle($record);
+ }
+
+ public function testInjectedGelfMessageFormatter()
+ {
+ $record = $this->getRecord(Logger::WARNING, "A test warning message");
+ $record['extra']['blarg'] = 'yep';
+ $record['context']['from'] = 'logger';
+
+ $expectedMessage = new Message();
+ $expectedMessage
+ ->setLevel(4)
+ ->setFacility("test")
+ ->setHost("mysystem")
+ ->setShortMessage($record['message'])
+ ->setTimestamp($record['datetime'])
+ ->setAdditional("EXTblarg", 'yep')
+ ->setAdditional("CTXfrom", 'logger')
+ ;
+
+ $messagePublisher = $this->getMessagePublisher();
+ $messagePublisher->expects($this->once())
+ ->method('publish')
+ ->with($expectedMessage);
+
+ $handler = $this->getHandler($messagePublisher);
+ $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX'));
+ $handler->handle($record);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php
new file mode 100644
index 0000000..873d92f
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.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\Handler;
+
+use Gelf\MessagePublisher;
+use Gelf\Message;
+
+class GelfMockMessagePublisher extends MessagePublisher
+{
+ public function publish(Message $message)
+ {
+ $this->lastMessage = $message;
+ }
+
+ public $lastMessage = null;
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php
new file mode 100644
index 0000000..a1b8617
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php
@@ -0,0 +1,112 @@
+
+ *
+ * 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;
+
+class GroupHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\GroupHandler::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorOnlyTakesHandler()
+ {
+ new GroupHandler(array(new TestHandler(), "foo"));
+ }
+
+ /**
+ * @covers Monolog\Handler\GroupHandler::__construct
+ * @covers Monolog\Handler\GroupHandler::handle
+ */
+ public function testHandle()
+ {
+ $testHandlers = array(new TestHandler(), new TestHandler());
+ $handler = new GroupHandler($testHandlers);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ foreach ($testHandlers as $test) {
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertTrue(count($test->getRecords()) === 2);
+ }
+ }
+
+ /**
+ * @covers Monolog\Handler\GroupHandler::handleBatch
+ */
+ public function testHandleBatch()
+ {
+ $testHandlers = array(new TestHandler(), new TestHandler());
+ $handler = new GroupHandler($testHandlers);
+ $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);
+ }
+ }
+
+ /**
+ * @covers Monolog\Handler\GroupHandler::isHandling
+ */
+ public function testIsHandling()
+ {
+ $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING));
+ $handler = new GroupHandler($testHandlers);
+ $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR)));
+ $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING)));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG)));
+ }
+
+ /**
+ * @covers Monolog\Handler\GroupHandler::handle
+ */
+ public function testHandleUsesProcessors()
+ {
+ $test = new TestHandler();
+ $handler = new GroupHandler(array($test));
+ $handler->pushProcessor(function ($record) {
+ $record['extra']['foo'] = true;
+
+ return $record;
+ });
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasWarningRecords());
+ $records = $test->getRecords();
+ $this->assertTrue($records[0]['extra']['foo']);
+ }
+
+ /**
+ * @covers Monolog\Handler\GroupHandler::handle
+ */
+ public function testHandleBatchUsesProcessors()
+ {
+ $testHandlers = array(new TestHandler(), new TestHandler());
+ $handler = new GroupHandler($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']);
+ }
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php
new file mode 100644
index 0000000..d8d0452
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php
@@ -0,0 +1,130 @@
+
+ *
+ * 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;
+
+/**
+ * @author Alexey Karapetov
+ */
+class HandlerWrapperTest extends TestCase
+{
+ /**
+ * @var HandlerWrapper
+ */
+ private $wrapper;
+
+ private $handler;
+
+ public function setUp()
+ {
+ parent::setUp();
+ $this->handler = $this->getMock('Monolog\\Handler\\HandlerInterface');
+ $this->wrapper = new HandlerWrapper($this->handler);
+ }
+
+ /**
+ * @return array
+ */
+ public function trueFalseDataProvider()
+ {
+ return array(
+ array(true),
+ array(false),
+ );
+ }
+
+ /**
+ * @param $result
+ * @dataProvider trueFalseDataProvider
+ */
+ public function testIsHandling($result)
+ {
+ $record = $this->getRecord();
+ $this->handler->expects($this->once())
+ ->method('isHandling')
+ ->with($record)
+ ->willReturn($result);
+
+ $this->assertEquals($result, $this->wrapper->isHandling($record));
+ }
+
+ /**
+ * @param $result
+ * @dataProvider trueFalseDataProvider
+ */
+ public function testHandle($result)
+ {
+ $record = $this->getRecord();
+ $this->handler->expects($this->once())
+ ->method('handle')
+ ->with($record)
+ ->willReturn($result);
+
+ $this->assertEquals($result, $this->wrapper->handle($record));
+ }
+
+ /**
+ * @param $result
+ * @dataProvider trueFalseDataProvider
+ */
+ public function testHandleBatch($result)
+ {
+ $records = $this->getMultipleRecords();
+ $this->handler->expects($this->once())
+ ->method('handleBatch')
+ ->with($records)
+ ->willReturn($result);
+
+ $this->assertEquals($result, $this->wrapper->handleBatch($records));
+ }
+
+ public function testPushProcessor()
+ {
+ $processor = function () {};
+ $this->handler->expects($this->once())
+ ->method('pushProcessor')
+ ->with($processor);
+
+ $this->assertEquals($this->wrapper, $this->wrapper->pushProcessor($processor));
+ }
+
+ public function testPopProcessor()
+ {
+ $processor = function () {};
+ $this->handler->expects($this->once())
+ ->method('popProcessor')
+ ->willReturn($processor);
+
+ $this->assertEquals($processor, $this->wrapper->popProcessor());
+ }
+
+ public function testSetFormatter()
+ {
+ $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $this->handler->expects($this->once())
+ ->method('setFormatter')
+ ->with($formatter);
+
+ $this->assertEquals($this->wrapper, $this->wrapper->setFormatter($formatter));
+ }
+
+ public function testGetFormatter()
+ {
+ $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $this->handler->expects($this->once())
+ ->method('getFormatter')
+ ->willReturn($formatter);
+
+ $this->assertEquals($formatter, $this->wrapper->getFormatter());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php
new file mode 100644
index 0000000..52dc9da
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php
@@ -0,0 +1,279 @@
+
+ *
+ * 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 Rafael Dohms
+ * @see https://www.hipchat.com/docs/api
+ */
+class HipChatHandlerTest extends TestCase
+{
+ private $res;
+ /** @var HipChatHandler */
+ private $handler;
+
+ public function testWriteHeader()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+
+ return $content;
+ }
+
+ public function testWriteCustomHostHeader()
+ {
+ $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar');
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+
+ return $content;
+ }
+
+ public function testWriteV2()
+ {
+ $this->createHandler('myToken', 'room1', 'Monolog', false, 'hipchat.foo.bar', 'v2');
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+
+ return $content;
+ }
+
+ public function testWriteV2Notify()
+ {
+ $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar', 'v2');
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+
+ return $content;
+ }
+
+ public function testRoomSpaces()
+ {
+ $this->createHandler('myToken', 'room name', 'Monolog', false, 'hipchat.foo.bar', 'v2');
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/POST \/v2\/room\/room%20name\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+
+ return $content;
+ }
+
+ /**
+ * @depends testWriteHeader
+ */
+ public function testWriteContent($content)
+ {
+ $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content);
+ }
+
+ public function testWriteContentV1WithoutName()
+ {
+ $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v1');
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=$/', $content);
+
+ return $content;
+ }
+
+ /**
+ * @depends testWriteCustomHostHeader
+ */
+ public function testWriteContentNotify($content)
+ {
+ $this->assertRegexp('/notify=1&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content);
+ }
+
+ /**
+ * @depends testWriteV2
+ */
+ public function testWriteContentV2($content)
+ {
+ $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red&from=Monolog$/', $content);
+ }
+
+ /**
+ * @depends testWriteV2Notify
+ */
+ public function testWriteContentV2Notify($content)
+ {
+ $this->assertRegexp('/notify=true&message=test1&message_format=text&color=red&from=Monolog$/', $content);
+ }
+
+ public function testWriteContentV2WithoutName()
+ {
+ $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v2');
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red$/', $content);
+
+ return $content;
+ }
+
+ public function testWriteWithComplexMessage()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content);
+ }
+
+ public function testWriteTruncatesLongMessage()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, str_repeat('abcde', 2000)));
+ fseek($this->res, 0);
+ $content = fread($this->res, 12000);
+
+ $this->assertRegexp('/message='.str_repeat('abcde', 1900).'\+%5Btruncated%5D/', $content);
+ }
+
+ /**
+ * @dataProvider provideLevelColors
+ */
+ public function testWriteWithErrorLevelsAndColors($level, $expectedColor)
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord($level, 'Backup of database "example" finished in 16 minutes.'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/color='.$expectedColor.'/', $content);
+ }
+
+ public function provideLevelColors()
+ {
+ return array(
+ array(Logger::DEBUG, 'gray'),
+ array(Logger::INFO, 'green'),
+ array(Logger::WARNING, 'yellow'),
+ array(Logger::ERROR, 'red'),
+ array(Logger::CRITICAL, 'red'),
+ array(Logger::ALERT, 'red'),
+ array(Logger::EMERGENCY,'red'),
+ array(Logger::NOTICE, 'green'),
+ );
+ }
+
+ /**
+ * @dataProvider provideBatchRecords
+ */
+ public function testHandleBatch($records, $expectedColor)
+ {
+ $this->createHandler();
+
+ $this->handler->handleBatch($records);
+
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/color='.$expectedColor.'/', $content);
+ }
+
+ public function provideBatchRecords()
+ {
+ return array(
+ array(
+ array(
+ array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()),
+ array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()),
+ array('level' => Logger::CRITICAL, 'message' => 'Everything is broken!', 'level_name' => 'critical', 'datetime' => new \DateTime()),
+ ),
+ 'red',
+ ),
+ array(
+ array(
+ array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()),
+ array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()),
+ ),
+ 'yellow',
+ ),
+ array(
+ array(
+ array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()),
+ array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()),
+ ),
+ 'green',
+ ),
+ array(
+ array(
+ array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()),
+ ),
+ 'gray',
+ ),
+ );
+ }
+
+ private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false, $host = 'api.hipchat.com', $version = 'v1')
+ {
+ $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG, true, true, 'text', $host, $version);
+ $this->res = fopen('php://memory', 'a');
+ $this->handler = $this->getMock(
+ '\Monolog\Handler\HipChatHandler',
+ array('fsockopen', 'streamSetTimeout', 'closeSocket'),
+ $constructorArgs
+ );
+
+ $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->res));
+ $this->handler->expects($this->any())
+ ->method('streamSetTimeout')
+ ->will($this->returnValue(true));
+ $this->handler->expects($this->any())
+ ->method('closeSocket')
+ ->will($this->returnValue(true));
+
+ $this->handler->setFormatter($this->getIdentityFormatter());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testCreateWithTooLongName()
+ {
+ $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere');
+ }
+
+ public function testCreateWithTooLongNameV2()
+ {
+ // creating a handler with too long of a name but using the v2 api doesn't matter.
+ $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere', false, Logger::CRITICAL, true, true, 'test', 'api.hipchat.com', 'v2');
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/InsightOpsHandlerTest.php
new file mode 100644
index 0000000..97c18b5
--- /dev/null
+++ b/core/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/core/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php
new file mode 100644
index 0000000..b2deb40
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php
@@ -0,0 +1,84 @@
+
+ *
+ * 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
+ */
+class LogEntriesHandlerTest extends TestCase
+{
+ /**
+ * @var resource
+ */
+ private $res;
+
+ /**
+ * @var LogEntriesHandler
+ */
+ private $handler;
+
+ public function testWriteContent()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test'));
+
+ fseek($this->res, 0);
+ $content = fread($this->res, 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()
+ {
+ $records = array(
+ $this->getRecord(),
+ $this->getRecord(),
+ $this->getRecord(),
+ );
+ $this->createHandler();
+ $this->handler->handleBatch($records);
+
+ fseek($this->res, 0);
+ $content = fread($this->res, 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', $useSSL, Logger::DEBUG, true);
+ $this->res = fopen('php://memory', 'a');
+ $this->handler = $this->getMock(
+ '\Monolog\Handler\LogEntriesHandler',
+ 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->res));
+ $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/core/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php
new file mode 100644
index 0000000..6754f3d
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+use Monolog\TestCase;
+
+class MailHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\MailHandler::handleBatch
+ */
+ public function testHandleBatch()
+ {
+ $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $formatter->expects($this->once())
+ ->method('formatBatch'); // Each record is formatted
+
+ $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler');
+ $handler->expects($this->once())
+ ->method('send');
+ $handler->expects($this->never())
+ ->method('write'); // write is for individual records
+
+ $handler->setFormatter($formatter);
+
+ $handler->handleBatch($this->getMultipleRecords());
+ }
+
+ /**
+ * @covers Monolog\Handler\MailHandler::handleBatch
+ */
+ public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel()
+ {
+ $records = array(
+ $this->getRecord(Logger::DEBUG, 'debug message 1'),
+ $this->getRecord(Logger::DEBUG, 'debug message 2'),
+ $this->getRecord(Logger::INFO, 'information'),
+ );
+
+ $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler');
+ $handler->expects($this->never())
+ ->method('send');
+ $handler->setLevel(Logger::ERROR);
+
+ $handler->handleBatch($records);
+ }
+
+ /**
+ * @covers Monolog\Handler\MailHandler::write
+ */
+ public function testHandle()
+ {
+ $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler');
+
+ $record = $this->getRecord();
+ $records = array($record);
+ $records[0]['formatted'] = '['.$record['datetime']->format('Y-m-d H:i:s').'] test.WARNING: test [] []'."\n";
+
+ $handler->expects($this->once())
+ ->method('send')
+ ->with($records[0]['formatted'], $records);
+
+ $handler->handle($record);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php
new file mode 100644
index 0000000..a083322
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Raven_Client;
+
+class MockRavenClient extends Raven_Client
+{
+ public function capture($data, $stack, $vars = null)
+ {
+ $data = array_merge($this->get_user_data(), $data);
+ $this->lastData = $data;
+ $this->lastStack = $stack;
+ }
+
+ public $lastData;
+ public $lastStack;
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php
new file mode 100644
index 0000000..0fdef63
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+class MongoDBHandlerTest extends TestCase
+{
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorShouldThrowExceptionForInvalidMongo()
+ {
+ new MongoDBHandler(new \stdClass(), 'DB', 'Collection');
+ }
+
+ public function testHandle()
+ {
+ $mongo = $this->getMock('Mongo', array('selectCollection'), array(), '', false);
+ $collection = $this->getMock('stdClass', array('save'));
+
+ $mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with('DB', 'Collection')
+ ->will($this->returnValue($collection));
+
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $expected = array(
+ 'message' => 'test',
+ 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34),
+ 'level' => Logger::WARNING,
+ 'level_name' => 'WARNING',
+ 'channel' => 'test',
+ 'datetime' => $record['datetime']->format('Y-m-d H:i:s'),
+ 'extra' => array(),
+ );
+
+ $collection->expects($this->once())
+ ->method('save')
+ ->with($expected);
+
+ $handler = new MongoDBHandler($mongo, 'DB', 'Collection');
+ $handler->handle($record);
+ }
+}
+
+if (!class_exists('Mongo')) {
+ class Mongo
+ {
+ public function selectCollection()
+ {
+ }
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php
new file mode 100644
index 0000000..ddf545d
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php
@@ -0,0 +1,111 @@
+
+ *
+ * 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;
+use InvalidArgumentException;
+
+function mail($to, $subject, $message, $additional_headers = null, $additional_parameters = null)
+{
+ $GLOBALS['mail'][] = func_get_args();
+}
+
+class NativeMailerHandlerTest extends TestCase
+{
+ protected function setUp()
+ {
+ $GLOBALS['mail'] = array();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorHeaderInjection()
+ {
+ $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', "receiver@example.org\r\nFrom: faked@attacker.org");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetterHeaderInjection()
+ {
+ $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');
+ $mailer->addHeader("Content-Type: text/html\r\nFrom: faked@attacker.org");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetterArrayHeaderInjection()
+ {
+ $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');
+ $mailer->addHeader(array("Content-Type: text/html\r\nFrom: faked@attacker.org"));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetterContentTypeInjection()
+ {
+ $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');
+ $mailer->setContentType("text/html\r\nFrom: faked@attacker.org");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetterEncodingInjection()
+ {
+ $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');
+ $mailer->setEncoding("utf-8\r\nFrom: faked@attacker.org");
+ }
+
+ public function testSend()
+ {
+ $to = 'spammer@example.org';
+ $subject = 'dear victim';
+ $from = 'receiver@example.org';
+
+ $mailer = new NativeMailerHandler($to, $subject, $from);
+ $mailer->handleBatch(array());
+
+ // batch is empty, nothing sent
+ $this->assertEmpty($GLOBALS['mail']);
+
+ // non-empty batch
+ $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz"));
+ $this->assertNotEmpty($GLOBALS['mail']);
+ $this->assertInternalType('array', $GLOBALS['mail']);
+ $this->assertArrayHasKey('0', $GLOBALS['mail']);
+ $params = $GLOBALS['mail'][0];
+ $this->assertCount(5, $params);
+ $this->assertSame($to, $params[0]);
+ $this->assertSame($subject, $params[1]);
+ $this->assertStringEndsWith(" test.ERROR: Foo Bar Baz [] []\n", $params[2]);
+ $this->assertSame("From: $from\r\nContent-type: text/plain; charset=utf-8\r\n", $params[3]);
+ $this->assertSame('', $params[4]);
+ }
+
+ public function testMessageSubjectFormatting()
+ {
+ $mailer = new NativeMailerHandler('to@example.org', 'Alert: %level_name% %message%', 'from@example.org');
+ $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz"));
+ $this->assertNotEmpty($GLOBALS['mail']);
+ $this->assertInternalType('array', $GLOBALS['mail']);
+ $this->assertArrayHasKey('0', $GLOBALS['mail']);
+ $params = $GLOBALS['mail'][0];
+ $this->assertCount(5, $params);
+ $this->assertSame('Alert: ERROR Foo Bar Baz', $params[1]);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php
new file mode 100644
index 0000000..4d3a615
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php
@@ -0,0 +1,200 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\TestCase;
+use Monolog\Logger;
+
+class NewRelicHandlerTest extends TestCase
+{
+ public static $appname;
+ public static $customParameters;
+ public static $transactionName;
+
+ public function setUp()
+ {
+ self::$appname = null;
+ self::$customParameters = array();
+ self::$transactionName = null;
+ }
+
+ /**
+ * @expectedException Monolog\Handler\MissingExtensionException
+ */
+ public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded()
+ {
+ $handler = new StubNewRelicHandlerWithoutExtension();
+ $handler->handle($this->getRecord(Logger::ERROR));
+ }
+
+ public function testThehandlerCanHandleTheRecord()
+ {
+ $handler = new StubNewRelicHandler();
+ $handler->handle($this->getRecord(Logger::ERROR));
+ }
+
+ public function testThehandlerCanAddContextParamsToTheNewRelicTrace()
+ {
+ $handler = new StubNewRelicHandler();
+ $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('a' => 'b')));
+ $this->assertEquals(array('context_a' => 'b'), self::$customParameters);
+ }
+
+ public function testThehandlerCanAddExplodedContextParamsToTheNewRelicTrace()
+ {
+ $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true);
+ $handler->handle($this->getRecord(
+ Logger::ERROR,
+ 'log message',
+ array('a' => array('key1' => 'value1', 'key2' => 'value2'))
+ ));
+ $this->assertEquals(
+ array('context_a_key1' => 'value1', 'context_a_key2' => 'value2'),
+ self::$customParameters
+ );
+ }
+
+ public function testThehandlerCanAddExtraParamsToTheNewRelicTrace()
+ {
+ $record = $this->getRecord(Logger::ERROR, 'log message');
+ $record['extra'] = array('c' => 'd');
+
+ $handler = new StubNewRelicHandler();
+ $handler->handle($record);
+
+ $this->assertEquals(array('extra_c' => 'd'), self::$customParameters);
+ }
+
+ public function testThehandlerCanAddExplodedExtraParamsToTheNewRelicTrace()
+ {
+ $record = $this->getRecord(Logger::ERROR, 'log message');
+ $record['extra'] = array('c' => array('key1' => 'value1', 'key2' => 'value2'));
+
+ $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true);
+ $handler->handle($record);
+
+ $this->assertEquals(
+ array('extra_c_key1' => 'value1', 'extra_c_key2' => 'value2'),
+ self::$customParameters
+ );
+ }
+
+ public function testThehandlerCanAddExtraContextAndParamsToTheNewRelicTrace()
+ {
+ $record = $this->getRecord(Logger::ERROR, 'log message', array('a' => 'b'));
+ $record['extra'] = array('c' => 'd');
+
+ $handler = new StubNewRelicHandler();
+ $handler->handle($record);
+
+ $expected = array(
+ 'context_a' => 'b',
+ 'extra_c' => 'd',
+ );
+
+ $this->assertEquals($expected, self::$customParameters);
+ }
+
+ public function testThehandlerCanHandleTheRecordsFormattedUsingTheLineFormatter()
+ {
+ $handler = new StubNewRelicHandler();
+ $handler->setFormatter(new LineFormatter());
+ $handler->handle($this->getRecord(Logger::ERROR));
+ }
+
+ public function testTheAppNameIsNullByDefault()
+ {
+ $handler = new StubNewRelicHandler();
+ $handler->handle($this->getRecord(Logger::ERROR, 'log message'));
+
+ $this->assertEquals(null, self::$appname);
+ }
+
+ public function testTheAppNameCanBeInjectedFromtheConstructor()
+ {
+ $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName');
+ $handler->handle($this->getRecord(Logger::ERROR, 'log message'));
+
+ $this->assertEquals('myAppName', self::$appname);
+ }
+
+ public function testTheAppNameCanBeOverriddenFromEachLog()
+ {
+ $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName');
+ $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('appname' => 'logAppName')));
+
+ $this->assertEquals('logAppName', self::$appname);
+ }
+
+ public function testTheTransactionNameIsNullByDefault()
+ {
+ $handler = new StubNewRelicHandler();
+ $handler->handle($this->getRecord(Logger::ERROR, 'log message'));
+
+ $this->assertEquals(null, self::$transactionName);
+ }
+
+ public function testTheTransactionNameCanBeInjectedFromTheConstructor()
+ {
+ $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction');
+ $handler->handle($this->getRecord(Logger::ERROR, 'log message'));
+
+ $this->assertEquals('myTransaction', self::$transactionName);
+ }
+
+ public function testTheTransactionNameCanBeOverriddenFromEachLog()
+ {
+ $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction');
+ $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('transaction_name' => 'logTransactName')));
+
+ $this->assertEquals('logTransactName', self::$transactionName);
+ }
+}
+
+class StubNewRelicHandlerWithoutExtension extends NewRelicHandler
+{
+ protected function isNewRelicEnabled()
+ {
+ return false;
+ }
+}
+
+class StubNewRelicHandler extends NewRelicHandler
+{
+ protected function isNewRelicEnabled()
+ {
+ return true;
+ }
+}
+
+function newrelic_notice_error()
+{
+ return true;
+}
+
+function newrelic_set_appname($appname)
+{
+ return NewRelicHandlerTest::$appname = $appname;
+}
+
+function newrelic_name_transaction($transactionName)
+{
+ return NewRelicHandlerTest::$transactionName = $transactionName;
+}
+
+function newrelic_add_custom_parameter($key, $value)
+{
+ NewRelicHandlerTest::$customParameters[$key] = $value;
+
+ return true;
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php
new file mode 100644
index 0000000..292df78
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+/**
+ * @covers Monolog\Handler\NullHandler::handle
+ */
+class NullHandlerTest extends TestCase
+{
+ public function testHandle()
+ {
+ $handler = new NullHandler();
+ $this->assertTrue($handler->handle($this->getRecord()));
+ }
+
+ public function testHandleLowerLevelRecord()
+ {
+ $handler = new NullHandler(Logger::WARNING);
+ $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG)));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php
new file mode 100644
index 0000000..152573e
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php
@@ -0,0 +1,273 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Exception;
+use Monolog\ErrorHandler;
+use Monolog\Logger;
+use Monolog\TestCase;
+use PhpConsole\Connector;
+use PhpConsole\Dispatcher\Debug as DebugDispatcher;
+use PhpConsole\Dispatcher\Errors as ErrorDispatcher;
+use PhpConsole\Handler;
+use PHPUnit_Framework_MockObject_MockObject;
+
+/**
+ * @covers Monolog\Handler\PHPConsoleHandler
+ * @author Sergey Barbushin https://www.linkedin.com/in/barbushin
+ */
+class PHPConsoleHandlerTest extends TestCase
+{
+ /** @var Connector|PHPUnit_Framework_MockObject_MockObject */
+ protected $connector;
+ /** @var DebugDispatcher|PHPUnit_Framework_MockObject_MockObject */
+ protected $debugDispatcher;
+ /** @var ErrorDispatcher|PHPUnit_Framework_MockObject_MockObject */
+ protected $errorDispatcher;
+
+ protected function setUp()
+ {
+ if (!class_exists('PhpConsole\Connector')) {
+ $this->markTestSkipped('PHP Console library not found. See https://github.com/barbushin/php-console#installation');
+ }
+ $this->connector = $this->initConnectorMock();
+
+ $this->debugDispatcher = $this->initDebugDispatcherMock($this->connector);
+ $this->connector->setDebugDispatcher($this->debugDispatcher);
+
+ $this->errorDispatcher = $this->initErrorDispatcherMock($this->connector);
+ $this->connector->setErrorsDispatcher($this->errorDispatcher);
+ }
+
+ protected function initDebugDispatcherMock(Connector $connector)
+ {
+ return $this->getMockBuilder('PhpConsole\Dispatcher\Debug')
+ ->disableOriginalConstructor()
+ ->setMethods(array('dispatchDebug'))
+ ->setConstructorArgs(array($connector, $connector->getDumper()))
+ ->getMock();
+ }
+
+ protected function initErrorDispatcherMock(Connector $connector)
+ {
+ return $this->getMockBuilder('PhpConsole\Dispatcher\Errors')
+ ->disableOriginalConstructor()
+ ->setMethods(array('dispatchError', 'dispatchException'))
+ ->setConstructorArgs(array($connector, $connector->getDumper()))
+ ->getMock();
+ }
+
+ protected function initConnectorMock()
+ {
+ $connector = $this->getMockBuilder('PhpConsole\Connector')
+ ->disableOriginalConstructor()
+ ->setMethods(array(
+ 'sendMessage',
+ 'onShutDown',
+ 'isActiveClient',
+ 'setSourcesBasePath',
+ 'setServerEncoding',
+ 'setPassword',
+ 'enableSslOnlyMode',
+ 'setAllowedIpMasks',
+ 'setHeadersLimit',
+ 'startEvalRequestsListener',
+ ))
+ ->getMock();
+
+ $connector->expects($this->any())
+ ->method('isActiveClient')
+ ->will($this->returnValue(true));
+
+ return $connector;
+ }
+
+ protected function getHandlerDefaultOption($name)
+ {
+ $handler = new PHPConsoleHandler(array(), $this->connector);
+ $options = $handler->getOptions();
+
+ return $options[$name];
+ }
+
+ protected function initLogger($handlerOptions = array(), $level = Logger::DEBUG)
+ {
+ return new Logger('test', array(
+ new PHPConsoleHandler($handlerOptions, $this->connector, $level),
+ ));
+ }
+
+ public function testInitWithDefaultConnector()
+ {
+ $handler = new PHPConsoleHandler();
+ $this->assertEquals(spl_object_hash(Connector::getInstance()), spl_object_hash($handler->getConnector()));
+ }
+
+ public function testInitWithCustomConnector()
+ {
+ $handler = new PHPConsoleHandler(array(), $this->connector);
+ $this->assertEquals(spl_object_hash($this->connector), spl_object_hash($handler->getConnector()));
+ }
+
+ public function testDebug()
+ {
+ $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with($this->equalTo('test'));
+ $this->initLogger()->addDebug('test');
+ }
+
+ public function testDebugContextInMessage()
+ {
+ $message = 'test';
+ $tag = 'tag';
+ $context = array($tag, 'custom' => mt_rand());
+ $expectedMessage = $message . ' ' . json_encode(array_slice($context, 1));
+ $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with(
+ $this->equalTo($expectedMessage),
+ $this->equalTo($tag)
+ );
+ $this->initLogger()->addDebug($message, $context);
+ }
+
+ public function testDebugTags($tagsContextKeys = null)
+ {
+ $expectedTags = mt_rand();
+ $logger = $this->initLogger($tagsContextKeys ? array('debugTagsKeysInContext' => $tagsContextKeys) : array());
+ if (!$tagsContextKeys) {
+ $tagsContextKeys = $this->getHandlerDefaultOption('debugTagsKeysInContext');
+ }
+ foreach ($tagsContextKeys as $key) {
+ $debugDispatcher = $this->initDebugDispatcherMock($this->connector);
+ $debugDispatcher->expects($this->once())->method('dispatchDebug')->with(
+ $this->anything(),
+ $this->equalTo($expectedTags)
+ );
+ $this->connector->setDebugDispatcher($debugDispatcher);
+ $logger->addDebug('test', array($key => $expectedTags));
+ }
+ }
+
+ public function testError($classesPartialsTraceIgnore = null)
+ {
+ $code = E_USER_NOTICE;
+ $message = 'message';
+ $file = __FILE__;
+ $line = __LINE__;
+ $this->errorDispatcher->expects($this->once())->method('dispatchError')->with(
+ $this->equalTo($code),
+ $this->equalTo($message),
+ $this->equalTo($file),
+ $this->equalTo($line),
+ $classesPartialsTraceIgnore ?: $this->equalTo($this->getHandlerDefaultOption('classesPartialsTraceIgnore'))
+ );
+ $errorHandler = ErrorHandler::register($this->initLogger($classesPartialsTraceIgnore ? array('classesPartialsTraceIgnore' => $classesPartialsTraceIgnore) : array()), false);
+ $errorHandler->registerErrorHandler(array(), false, E_USER_WARNING);
+ $errorHandler->handleError($code, $message, $file, $line);
+ }
+
+ public function testException()
+ {
+ $e = new Exception();
+ $this->errorDispatcher->expects($this->once())->method('dispatchException')->with(
+ $this->equalTo($e)
+ );
+ $handler = $this->initLogger();
+ $handler->log(
+ \Psr\Log\LogLevel::ERROR,
+ sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()),
+ array('exception' => $e)
+ );
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testWrongOptionsThrowsException()
+ {
+ new PHPConsoleHandler(array('xxx' => 1));
+ }
+
+ public function testOptionEnabled()
+ {
+ $this->debugDispatcher->expects($this->never())->method('dispatchDebug');
+ $this->initLogger(array('enabled' => false))->addDebug('test');
+ }
+
+ public function testOptionClassesPartialsTraceIgnore()
+ {
+ $this->testError(array('Class', 'Namespace\\'));
+ }
+
+ public function testOptionDebugTagsKeysInContext()
+ {
+ $this->testDebugTags(array('key1', 'key2'));
+ }
+
+ public function testOptionUseOwnErrorsAndExceptionsHandler()
+ {
+ $this->initLogger(array('useOwnErrorsHandler' => true, 'useOwnExceptionsHandler' => true));
+ $this->assertEquals(array(Handler::getInstance(), 'handleError'), set_error_handler(function () {
+ }));
+ $this->assertEquals(array(Handler::getInstance(), 'handleException'), set_exception_handler(function () {
+ }));
+ }
+
+ public static function provideConnectorMethodsOptionsSets()
+ {
+ return array(
+ array('sourcesBasePath', 'setSourcesBasePath', __DIR__),
+ array('serverEncoding', 'setServerEncoding', 'cp1251'),
+ array('password', 'setPassword', '******'),
+ array('enableSslOnlyMode', 'enableSslOnlyMode', true, false),
+ array('ipMasks', 'setAllowedIpMasks', array('127.0.0.*')),
+ array('headersLimit', 'setHeadersLimit', 2500),
+ array('enableEvalListener', 'startEvalRequestsListener', true, false),
+ );
+ }
+
+ /**
+ * @dataProvider provideConnectorMethodsOptionsSets
+ */
+ public function testOptionCallsConnectorMethod($option, $method, $value, $isArgument = true)
+ {
+ $expectCall = $this->connector->expects($this->once())->method($method);
+ if ($isArgument) {
+ $expectCall->with($value);
+ }
+ new PHPConsoleHandler(array($option => $value), $this->connector);
+ }
+
+ public function testOptionDetectDumpTraceAndSource()
+ {
+ new PHPConsoleHandler(array('detectDumpTraceAndSource' => true), $this->connector);
+ $this->assertTrue($this->connector->getDebugDispatcher()->detectTraceAndSource);
+ }
+
+ public static function provideDumperOptionsValues()
+ {
+ return array(
+ array('dumperLevelLimit', 'levelLimit', 1001),
+ array('dumperItemsCountLimit', 'itemsCountLimit', 1002),
+ array('dumperItemSizeLimit', 'itemSizeLimit', 1003),
+ array('dumperDumpSizeLimit', 'dumpSizeLimit', 1004),
+ array('dumperDetectCallbacks', 'detectCallbacks', true),
+ );
+ }
+
+ /**
+ * @dataProvider provideDumperOptionsValues
+ */
+ public function testDumperOptions($option, $dumperProperty, $value)
+ {
+ new PHPConsoleHandler(array($option => $value), $this->connector);
+ $this->assertEquals($value, $this->connector->getDumper()->$dumperProperty);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php
new file mode 100644
index 0000000..64eaab1
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+/**
+ * @covers Monolog\Handler\PsrHandler::handle
+ */
+class PsrHandlerTest extends TestCase
+{
+ public function logLevelProvider()
+ {
+ $levels = array();
+ $monologLogger = new Logger('');
+
+ foreach ($monologLogger->getLevels() as $levelName => $level) {
+ $levels[] = array($levelName, $level);
+ }
+
+ return $levels;
+ }
+
+ /**
+ * @dataProvider logLevelProvider
+ */
+ public function testHandlesAllLevels($levelName, $level)
+ {
+ $message = 'Hello, world! ' . $level;
+ $context = array('foo' => 'bar', 'level' => $level);
+
+ $psrLogger = $this->getMock('Psr\Log\NullLogger');
+ $psrLogger->expects($this->once())
+ ->method('log')
+ ->with(strtolower($levelName), $message, $context);
+
+ $handler = new PsrHandler($psrLogger);
+ $handler->handle(array('level' => $level, 'level_name' => $levelName, 'message' => $message, 'context' => $context));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php
new file mode 100644
index 0000000..56df474
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+/**
+ * Almost all examples (expected header, titles, messages) taken from
+ * https://www.pushover.net/api
+ * @author Sebastian Göttschkes
+ * @see https://www.pushover.net/api
+ */
+class PushoverHandlerTest extends TestCase
+{
+ private $res;
+ private $handler;
+
+ public function testWriteHeader()
+ {
+ $this->createHandler();
+ $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/POST \/1\/messages.json HTTP\/1.1\\r\\nHost: api.pushover.net\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+
+ return $content;
+ }
+
+ /**
+ * @depends testWriteHeader
+ */
+ public function testWriteContent($content)
+ {
+ $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}$/', $content);
+ }
+
+ public function testWriteWithComplexTitle()
+ {
+ $this->createHandler('myToken', 'myUser', 'Backup finished - SQL1');
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/title=Backup\+finished\+-\+SQL1/', $content);
+ }
+
+ public function testWriteWithComplexMessage()
+ {
+ $this->createHandler();
+ $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content);
+ }
+
+ public function testWriteWithTooLongMessage()
+ {
+ $message = str_pad('test', 520, 'a');
+ $this->createHandler();
+ $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, $message));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $expectedMessage = substr($message, 0, 505);
+
+ $this->assertRegexp('/message=' . $expectedMessage . '&title/', $content);
+ }
+
+ public function testWriteWithHighPriority()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=1$/', $content);
+ }
+
+ public function testWriteWithEmergencyPriority()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content);
+ }
+
+ public function testWriteToMultipleUsers()
+ {
+ $this->createHandler('myToken', array('userA', 'userB'));
+ $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/token=myToken&user=userA&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200POST/', $content);
+ $this->assertRegexp('/token=myToken&user=userB&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content);
+ }
+
+ private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog')
+ {
+ $constructorArgs = array($token, $user, $title);
+ $this->res = fopen('php://memory', 'a');
+ $this->handler = $this->getMock(
+ '\Monolog\Handler\PushoverHandler',
+ array('fsockopen', 'streamSetTimeout', 'closeSocket'),
+ $constructorArgs
+ );
+
+ $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->res));
+ $this->handler->expects($this->any())
+ ->method('streamSetTimeout')
+ ->will($this->returnValue(true));
+ $this->handler->expects($this->any())
+ ->method('closeSocket')
+ ->will($this->returnValue(true));
+
+ $this->handler->setFormatter($this->getIdentityFormatter());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php
new file mode 100644
index 0000000..26d212b
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php
@@ -0,0 +1,255 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+use Monolog\Formatter\LineFormatter;
+
+class RavenHandlerTest extends TestCase
+{
+ public function setUp()
+ {
+ if (!class_exists('Raven_Client')) {
+ $this->markTestSkipped('raven/raven not installed');
+ }
+
+ require_once __DIR__ . '/MockRavenClient.php';
+ }
+
+ /**
+ * @covers Monolog\Handler\RavenHandler::__construct
+ */
+ public function testConstruct()
+ {
+ $handler = new RavenHandler($this->getRavenClient());
+ $this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler);
+ }
+
+ protected function getHandler($ravenClient)
+ {
+ $handler = new RavenHandler($ravenClient);
+
+ return $handler;
+ }
+
+ protected function getRavenClient()
+ {
+ $dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1';
+
+ return new MockRavenClient($dsn);
+ }
+
+ public function testDebug()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+
+ $record = $this->getRecord(Logger::DEBUG, 'A test debug message');
+ $handler->handle($record);
+
+ $this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']);
+ $this->assertContains($record['message'], $ravenClient->lastData['message']);
+ }
+
+ public function testWarning()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+
+ $record = $this->getRecord(Logger::WARNING, 'A test warning message');
+ $handler->handle($record);
+
+ $this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']);
+ $this->assertContains($record['message'], $ravenClient->lastData['message']);
+ }
+
+ public function testTag()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+
+ $tags = array(1, 2, 'foo');
+ $record = $this->getRecord(Logger::INFO, 'test', array('tags' => $tags));
+ $handler->handle($record);
+
+ $this->assertEquals($tags, $ravenClient->lastData['tags']);
+ }
+
+ public function testExtraParameters()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+
+ $checksum = '098f6bcd4621d373cade4e832627b4f6';
+ $release = '05a671c66aefea124cc08b76ea6d30bb';
+ $eventId = '31423';
+ $record = $this->getRecord(Logger::INFO, 'test', array('checksum' => $checksum, 'release' => $release, 'event_id' => $eventId));
+ $handler->handle($record);
+
+ $this->assertEquals($checksum, $ravenClient->lastData['checksum']);
+ $this->assertEquals($release, $ravenClient->lastData['release']);
+ $this->assertEquals($eventId, $ravenClient->lastData['event_id']);
+ }
+
+ public function testFingerprint()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+
+ $fingerprint = array('{{ default }}', 'other value');
+ $record = $this->getRecord(Logger::INFO, 'test', array('fingerprint' => $fingerprint));
+ $handler->handle($record);
+
+ $this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']);
+ }
+
+ public function testUserContext()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+
+ $recordWithNoContext = $this->getRecord(Logger::INFO, 'test with default user context');
+ // set user context 'externally'
+
+ $user = array(
+ 'id' => '123',
+ 'email' => 'test@test.com',
+ );
+
+ $recordWithContext = $this->getRecord(Logger::INFO, 'test', array('user' => $user));
+
+ $ravenClient->user_context(array('id' => 'test_user_id'));
+ // handle context
+ $handler->handle($recordWithContext);
+ $this->assertEquals($user, $ravenClient->lastData['user']);
+
+ // check to see if its reset
+ $handler->handle($recordWithNoContext);
+ $this->assertInternalType('array', $ravenClient->context->user);
+ $this->assertSame('test_user_id', $ravenClient->context->user['id']);
+
+ // handle with null context
+ $ravenClient->user_context(null);
+ $handler->handle($recordWithContext);
+ $this->assertEquals($user, $ravenClient->lastData['user']);
+
+ // check to see if its reset
+ $handler->handle($recordWithNoContext);
+ $this->assertNull($ravenClient->context->user);
+ }
+
+ public function testException()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+
+ try {
+ $this->methodThatThrowsAnException();
+ } catch (\Exception $e) {
+ $record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e));
+ $handler->handle($record);
+ }
+
+ $this->assertEquals($record['message'], $ravenClient->lastData['message']);
+ }
+
+ public function testHandleBatch()
+ {
+ $records = $this->getMultipleRecords();
+ $records[] = $this->getRecord(Logger::WARNING, 'warning');
+ $records[] = $this->getRecord(Logger::WARNING, 'warning');
+
+ $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $logFormatter->expects($this->once())->method('formatBatch');
+
+ $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) {
+ return $record['level'] == 400;
+ }));
+
+ $handler = $this->getHandler($this->getRavenClient());
+ $handler->setBatchFormatter($logFormatter);
+ $handler->setFormatter($formatter);
+ $handler->handleBatch($records);
+ }
+
+ public function testHandleBatchDoNothingIfRecordsAreBelowLevel()
+ {
+ $records = array(
+ $this->getRecord(Logger::DEBUG, 'debug message 1'),
+ $this->getRecord(Logger::DEBUG, 'debug message 2'),
+ $this->getRecord(Logger::INFO, 'information'),
+ );
+
+ $handler = $this->getMock('Monolog\Handler\RavenHandler', null, array($this->getRavenClient()));
+ $handler->expects($this->never())->method('handle');
+ $handler->setLevel(Logger::ERROR);
+ $handler->handleBatch($records);
+ }
+
+ public function testHandleBatchPicksProperMessage()
+ {
+ $records = array(
+ $this->getRecord(Logger::DEBUG, 'debug message 1'),
+ $this->getRecord(Logger::DEBUG, 'debug message 2'),
+ $this->getRecord(Logger::INFO, 'information 1'),
+ $this->getRecord(Logger::ERROR, 'error 1'),
+ $this->getRecord(Logger::WARNING, 'warning'),
+ $this->getRecord(Logger::ERROR, 'error 2'),
+ $this->getRecord(Logger::INFO, 'information 2'),
+ );
+
+ $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $logFormatter->expects($this->once())->method('formatBatch');
+
+ $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) use ($records) {
+ return $record['message'] == 'error 1';
+ }));
+
+ $handler = $this->getHandler($this->getRavenClient());
+ $handler->setBatchFormatter($logFormatter);
+ $handler->setFormatter($formatter);
+ $handler->handleBatch($records);
+ }
+
+ public function testGetSetBatchFormatter()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+
+ $handler->setBatchFormatter($formatter = new LineFormatter());
+ $this->assertSame($formatter, $handler->getBatchFormatter());
+ }
+
+ public function testRelease()
+ {
+ $ravenClient = $this->getRavenClient();
+ $handler = $this->getHandler($ravenClient);
+ $release = 'v42.42.42';
+ $handler->setRelease($release);
+ $record = $this->getRecord(Logger::INFO, 'test');
+ $handler->handle($record);
+ $this->assertEquals($release, $ravenClient->lastData['release']);
+
+ $localRelease = 'v41.41.41';
+ $record = $this->getRecord(Logger::INFO, 'test', array('release' => $localRelease));
+ $handler->handle($record);
+ $this->assertEquals($localRelease, $ravenClient->lastData['release']);
+ }
+
+ private function methodThatThrowsAnException()
+ {
+ throw new \Exception('This is an exception');
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php
new file mode 100644
index 0000000..689d527
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php
@@ -0,0 +1,127 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+use Monolog\Formatter\LineFormatter;
+
+class RedisHandlerTest extends TestCase
+{
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorShouldThrowExceptionForInvalidRedis()
+ {
+ new RedisHandler(new \stdClass(), 'key');
+ }
+
+ public function testConstructorShouldWorkWithPredis()
+ {
+ $redis = $this->getMock('Predis\Client');
+ $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key'));
+ }
+
+ public function testConstructorShouldWorkWithRedis()
+ {
+ $redis = $this->getMock('Redis');
+ $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key'));
+ }
+
+ public function testPredisHandle()
+ {
+ $redis = $this->getMock('Predis\Client', array('rpush'));
+
+ // Predis\Client uses rpush
+ $redis->expects($this->once())
+ ->method('rpush')
+ ->with('key', 'test');
+
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $handler = new RedisHandler($redis, 'key');
+ $handler->setFormatter(new LineFormatter("%message%"));
+ $handler->handle($record);
+ }
+
+ public function testRedisHandle()
+ {
+ $redis = $this->getMock('Redis', array('rpush'));
+
+ // Redis uses rPush
+ $redis->expects($this->once())
+ ->method('rPush')
+ ->with('key', 'test');
+
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $handler = new RedisHandler($redis, 'key');
+ $handler->setFormatter(new LineFormatter("%message%"));
+ $handler->handle($record);
+ }
+
+ public function testRedisHandleCapped()
+ {
+ $redis = $this->getMock('Redis', array('multi', 'rpush', 'ltrim', 'exec'));
+
+ // Redis uses multi
+ $redis->expects($this->once())
+ ->method('multi')
+ ->will($this->returnSelf());
+
+ $redis->expects($this->once())
+ ->method('rpush')
+ ->will($this->returnSelf());
+
+ $redis->expects($this->once())
+ ->method('ltrim')
+ ->will($this->returnSelf());
+
+ $redis->expects($this->once())
+ ->method('exec')
+ ->will($this->returnSelf());
+
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10);
+ $handler->setFormatter(new LineFormatter("%message%"));
+ $handler->handle($record);
+ }
+
+ public function testPredisHandleCapped()
+ {
+ $redis = $this->getMock('Predis\Client', array('transaction'));
+
+ $redisTransaction = $this->getMock('Predis\Client', array('rpush', 'ltrim'));
+
+ $redisTransaction->expects($this->once())
+ ->method('rpush')
+ ->will($this->returnSelf());
+
+ $redisTransaction->expects($this->once())
+ ->method('ltrim')
+ ->will($this->returnSelf());
+
+ // Redis uses multi
+ $redis->expects($this->once())
+ ->method('transaction')
+ ->will($this->returnCallback(function ($cb) use ($redisTransaction) {
+ $cb($redisTransaction);
+ }));
+
+ $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
+
+ $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10);
+ $handler->setFormatter(new LineFormatter("%message%"));
+ $handler->handle($record);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php
new file mode 100644
index 0000000..f302e91
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Exception;
+use Monolog\TestCase;
+use Monolog\Logger;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+
+/**
+ * @author Erik Johansson
+ * @see https://rollbar.com/docs/notifier/rollbar-php/
+ *
+ * @coversDefaultClass Monolog\Handler\RollbarHandler
+ */
+class RollbarHandlerTest extends TestCase
+{
+ /**
+ * @var MockObject
+ */
+ private $rollbarNotifier;
+
+ /**
+ * @var array
+ */
+ public $reportedExceptionArguments = null;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->setupRollbarNotifierMock();
+ }
+
+ /**
+ * When reporting exceptions to Rollbar the
+ * level has to be set in the payload data
+ */
+ public function testExceptionLogLevel()
+ {
+ $handler = $this->createHandler();
+
+ $handler->handle($this->createExceptionRecord(Logger::DEBUG));
+
+ $this->assertEquals('debug', $this->reportedExceptionArguments['payload']['level']);
+ }
+
+ private function setupRollbarNotifierMock()
+ {
+ $this->rollbarNotifier = $this->getMockBuilder('RollbarNotifier')
+ ->setMethods(array('report_message', 'report_exception', 'flush'))
+ ->getMock();
+
+ $that = $this;
+
+ $this->rollbarNotifier
+ ->expects($this->any())
+ ->method('report_exception')
+ ->willReturnCallback(function ($exception, $context, $payload) use ($that) {
+ $that->reportedExceptionArguments = compact('exception', 'context', 'payload');
+ });
+ }
+
+ private function createHandler()
+ {
+ return new RollbarHandler($this->rollbarNotifier, Logger::DEBUG);
+ }
+
+ private function createExceptionRecord($level = Logger::DEBUG, $message = 'test', $exception = null)
+ {
+ return $this->getRecord($level, $message, array(
+ 'exception' => $exception ?: new Exception()
+ ));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php
new file mode 100644
index 0000000..c6f5fac
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php
@@ -0,0 +1,245 @@
+
+ *
+ * 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 PHPUnit_Framework_Error_Deprecated;
+
+/**
+ * @covers Monolog\Handler\RotatingFileHandler
+ */
+class RotatingFileHandlerTest extends TestCase
+{
+ /**
+ * This var should be private but then the anonymous function
+ * in the `setUp` method won't be able to set it. `$this` cant't
+ * be used in the anonymous function in `setUp` because PHP 5.3
+ * does not support it.
+ */
+ public $lastError;
+
+ public function setUp()
+ {
+ $dir = __DIR__.'/Fixtures';
+ chmod($dir, 0777);
+ if (!is_writable($dir)) {
+ $this->markTestSkipped($dir.' must be writable to test the RotatingFileHandler.');
+ }
+ $this->lastError = null;
+ $self = $this;
+ // workaround with &$self used for PHP 5.3
+ set_error_handler(function($code, $message) use (&$self) {
+ $self->lastError = array(
+ 'code' => $code,
+ 'message' => $message,
+ );
+ });
+ }
+
+ private function assertErrorWasTriggered($code, $message)
+ {
+ if (empty($this->lastError)) {
+ $this->fail(
+ sprintf(
+ 'Failed asserting that error with code `%d` and message `%s` was triggered',
+ $code,
+ $message
+ )
+ );
+ }
+ $this->assertEquals($code, $this->lastError['code'], sprintf('Expected an error with code %d to be triggered, got `%s` instead', $code, $this->lastError['code']));
+ $this->assertEquals($message, $this->lastError['message'], sprintf('Expected an error with message `%d` to be triggered, got `%s` instead', $message, $this->lastError['message']));
+ }
+
+ public function testRotationCreatesNewFile()
+ {
+ touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot');
+
+ $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot');
+ $handler->setFormatter($this->getIdentityFormatter());
+ $handler->handle($this->getRecord());
+
+ $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';
+ $this->assertTrue(file_exists($log));
+ $this->assertEquals('test', file_get_contents($log));
+ }
+
+ /**
+ * @dataProvider rotationTests
+ */
+ public function testRotation($createFile, $dateFormat, $timeCallback)
+ {
+ touch($old1 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-1)).'.rot');
+ touch($old2 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-2)).'.rot');
+ touch($old3 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-3)).'.rot');
+ touch($old4 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-4)).'.rot');
+
+ $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot';
+
+ if ($createFile) {
+ touch($log);
+ }
+
+ $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));
+ $this->assertTrue(file_exists($old1));
+ $this->assertEquals($createFile, file_exists($old2));
+ $this->assertEquals($createFile, file_exists($old3));
+ $this->assertEquals($createFile, file_exists($old4));
+ $this->assertEquals('test', file_get_contents($log));
+ }
+
+ public function rotationTests()
+ {
+ $now = time();
+ $dayCallback = function($ago) use ($now) {
+ return $now + 86400 * $ago;
+ };
+ $monthCallback = function($ago) {
+ return gmmktime(0, 0, 0, date('n') + $ago, 1, date('Y'));
+ };
+ $yearCallback = function($ago) {
+ return gmmktime(0, 0, 0, 1, 1, date('Y') + $ago);
+ };
+
+ return array(
+ 'Rotation is triggered when the file of the current day is not present'
+ => array(true, RotatingFileHandler::FILE_PER_DAY, $dayCallback),
+ 'Rotation is not triggered when the file of the current day is already present'
+ => array(false, RotatingFileHandler::FILE_PER_DAY, $dayCallback),
+
+ 'Rotation is triggered when the file of the current month is not present'
+ => array(true, RotatingFileHandler::FILE_PER_MONTH, $monthCallback),
+ 'Rotation is not triggered when the file of the current month is already present'
+ => array(false, RotatingFileHandler::FILE_PER_MONTH, $monthCallback),
+
+ 'Rotation is triggered when the file of the current year is not present'
+ => array(true, RotatingFileHandler::FILE_PER_YEAR, $yearCallback),
+ 'Rotation is not triggered when the file of the current year is already present'
+ => array(false, RotatingFileHandler::FILE_PER_YEAR, $yearCallback),
+ );
+ }
+
+ /**
+ * @dataProvider dateFormatProvider
+ */
+ public function testAllowOnlyFixedDefinedDateFormats($dateFormat, $valid)
+ {
+ $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);
+ $handler->setFilenameFormat('{filename}-{date}', $dateFormat);
+ if (!$valid) {
+ $this->assertErrorWasTriggered(
+ E_USER_DEPRECATED,
+ 'Invalid date format - format must be one of RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), '.
+ 'RotatingFileHandler::FILE_PER_MONTH ("Y-m") or RotatingFileHandler::FILE_PER_YEAR ("Y"), '.
+ 'or you can set one of the date formats using slashes, underscores and/or dots instead of dashes.'
+ );
+ }
+ }
+
+ public function dateFormatProvider()
+ {
+ return array(
+ array(RotatingFileHandler::FILE_PER_DAY, true),
+ array(RotatingFileHandler::FILE_PER_MONTH, true),
+ array(RotatingFileHandler::FILE_PER_YEAR, true),
+ array('m-d-Y', false),
+ array('Y-m-d-h-i', false)
+ );
+ }
+
+ /**
+ * @dataProvider filenameFormatProvider
+ */
+ public function testDisallowFilenameFormatsWithoutDate($filenameFormat, $valid)
+ {
+ $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);
+ $handler->setFilenameFormat($filenameFormat, RotatingFileHandler::FILE_PER_DAY);
+ if (!$valid) {
+ $this->assertErrorWasTriggered(
+ E_USER_DEPRECATED,
+ 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.'
+ );
+ }
+ }
+
+ public function filenameFormatProvider()
+ {
+ return array(
+ array('{filename}', false),
+ array('{filename}-{date}', true),
+ array('{date}', true),
+ array('foobar-{date}', true),
+ array('foo-{date}-bar', true),
+ array('{date}-foobar', true),
+ array('foobar', false),
+ );
+ }
+
+ /**
+ * @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';
+ file_put_contents($log, "foo");
+ $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot');
+ $handler->setFormatter($this->getIdentityFormatter());
+ $handler->handle($this->getRecord());
+ $this->assertEquals('footest', file_get_contents($log));
+ }
+
+ public function tearDown()
+ {
+ foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) {
+ unlink($file);
+ }
+ restore_error_handler();
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php
new file mode 100644
index 0000000..b354cee
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+
+/**
+ * @covers Monolog\Handler\SamplingHandler::handle
+ */
+class SamplingHandlerTest extends TestCase
+{
+ public function testHandle()
+ {
+ $testHandler = new TestHandler();
+ $handler = new SamplingHandler($testHandler, 2);
+ for ($i = 0; $i < 10000; $i++) {
+ $handler->handle($this->getRecord());
+ }
+ $count = count($testHandler->getRecords());
+ // $count should be half of 10k, so between 4k and 6k
+ $this->assertLessThan(6000, $count);
+ $this->assertGreaterThan(4000, $count);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php
new file mode 100644
index 0000000..b9de736
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php
@@ -0,0 +1,395 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler\Slack;
+
+use Monolog\Logger;
+use Monolog\TestCase;
+
+/**
+ * @coversDefaultClass Monolog\Handler\Slack\SlackRecord
+ */
+class SlackRecordTest extends TestCase
+{
+ private $jsonPrettyPrintFlag;
+
+ protected function setUp()
+ {
+ $this->jsonPrettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128;
+ }
+
+ public function dataGetAttachmentColor()
+ {
+ return array(
+ array(Logger::DEBUG, SlackRecord::COLOR_DEFAULT),
+ array(Logger::INFO, SlackRecord::COLOR_GOOD),
+ array(Logger::NOTICE, SlackRecord::COLOR_GOOD),
+ array(Logger::WARNING, SlackRecord::COLOR_WARNING),
+ array(Logger::ERROR, SlackRecord::COLOR_DANGER),
+ array(Logger::CRITICAL, SlackRecord::COLOR_DANGER),
+ array(Logger::ALERT, SlackRecord::COLOR_DANGER),
+ array(Logger::EMERGENCY, SlackRecord::COLOR_DANGER),
+ );
+ }
+
+ /**
+ * @dataProvider dataGetAttachmentColor
+ * @param int $logLevel
+ * @param string $expectedColour RGB hex color or name of Slack color
+ * @covers ::getAttachmentColor
+ */
+ public function testGetAttachmentColor($logLevel, $expectedColour)
+ {
+ $slackRecord = new SlackRecord();
+ $this->assertSame(
+ $expectedColour,
+ $slackRecord->getAttachmentColor($logLevel)
+ );
+ }
+
+ public function testAddsChannel()
+ {
+ $channel = '#test';
+ $record = new SlackRecord($channel);
+ $data = $record->getSlackData($this->getRecord());
+
+ $this->assertArrayHasKey('channel', $data);
+ $this->assertSame($channel, $data['channel']);
+ }
+
+ public function testNoUsernameByDefault()
+ {
+ $record = new SlackRecord();
+ $data = $record->getSlackData($this->getRecord());
+
+ $this->assertArrayNotHasKey('username', $data);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataStringify()
+ {
+ $jsonPrettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128;
+
+ $multipleDimensions = array(array(1, 2));
+ $numericKeys = array('library' => 'monolog');
+ $singleDimension = array(1, 'Hello', 'Jordi');
+
+ return array(
+ array(array(), '[]'),
+ array($multipleDimensions, json_encode($multipleDimensions, $jsonPrettyPrintFlag)),
+ array($numericKeys, json_encode($numericKeys, $jsonPrettyPrintFlag)),
+ array($singleDimension, json_encode($singleDimension))
+ );
+ }
+
+ /**
+ * @dataProvider dataStringify
+ */
+ public function testStringify($fields, $expectedResult)
+ {
+ $slackRecord = new SlackRecord(
+ '#test',
+ 'test',
+ true,
+ null,
+ true,
+ true
+ );
+
+ $this->assertSame($expectedResult, $slackRecord->stringify($fields));
+ }
+
+ public function testAddsCustomUsername()
+ {
+ $username = 'Monolog bot';
+ $record = new SlackRecord(null, $username);
+ $data = $record->getSlackData($this->getRecord());
+
+ $this->assertArrayHasKey('username', $data);
+ $this->assertSame($username, $data['username']);
+ }
+
+ public function testNoIcon()
+ {
+ $record = new SlackRecord();
+ $data = $record->getSlackData($this->getRecord());
+
+ $this->assertArrayNotHasKey('icon_emoji', $data);
+ }
+
+ public function testAddsIcon()
+ {
+ $record = $this->getRecord();
+ $slackRecord = new SlackRecord(null, null, false, 'ghost');
+ $data = $slackRecord->getSlackData($record);
+
+ $slackRecord2 = new SlackRecord(null, null, false, 'http://github.com/Seldaek/monolog');
+ $data2 = $slackRecord2->getSlackData($record);
+
+ $this->assertArrayHasKey('icon_emoji', $data);
+ $this->assertSame(':ghost:', $data['icon_emoji']);
+ $this->assertArrayHasKey('icon_url', $data2);
+ $this->assertSame('http://github.com/Seldaek/monolog', $data2['icon_url']);
+ }
+
+ public function testAttachmentsNotPresentIfNoAttachment()
+ {
+ $record = new SlackRecord(null, null, false);
+ $data = $record->getSlackData($this->getRecord());
+
+ $this->assertArrayNotHasKey('attachments', $data);
+ }
+
+ public function testAddsOneAttachment()
+ {
+ $record = new SlackRecord();
+ $data = $record->getSlackData($this->getRecord());
+
+ $this->assertArrayHasKey('attachments', $data);
+ $this->assertArrayHasKey(0, $data['attachments']);
+ $this->assertInternalType('array', $data['attachments'][0]);
+ }
+
+ public function testTextEqualsMessageIfNoAttachment()
+ {
+ $message = 'Test message';
+ $record = new SlackRecord(null, null, false);
+ $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message));
+
+ $this->assertArrayHasKey('text', $data);
+ $this->assertSame($message, $data['text']);
+ }
+
+ public function testTextEqualsFormatterOutput()
+ {
+ $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $formatter
+ ->expects($this->any())
+ ->method('format')
+ ->will($this->returnCallback(function ($record) { return $record['message'] . 'test'; }));
+
+ $formatter2 = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $formatter2
+ ->expects($this->any())
+ ->method('format')
+ ->will($this->returnCallback(function ($record) { return $record['message'] . 'test1'; }));
+
+ $message = 'Test message';
+ $record = new SlackRecord(null, null, false, null, false, false, array(), $formatter);
+ $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message));
+
+ $this->assertArrayHasKey('text', $data);
+ $this->assertSame($message . 'test', $data['text']);
+
+ $record->setFormatter($formatter2);
+ $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message));
+
+ $this->assertArrayHasKey('text', $data);
+ $this->assertSame($message . 'test1', $data['text']);
+ }
+
+ public function testAddsFallbackAndTextToAttachment()
+ {
+ $message = 'Test message';
+ $record = new SlackRecord(null);
+ $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message));
+
+ $this->assertSame($message, $data['attachments'][0]['text']);
+ $this->assertSame($message, $data['attachments'][0]['fallback']);
+ }
+
+ public function testMapsLevelToColorAttachmentColor()
+ {
+ $record = new SlackRecord(null);
+ $errorLoggerRecord = $this->getRecord(Logger::ERROR);
+ $emergencyLoggerRecord = $this->getRecord(Logger::EMERGENCY);
+ $warningLoggerRecord = $this->getRecord(Logger::WARNING);
+ $infoLoggerRecord = $this->getRecord(Logger::INFO);
+ $debugLoggerRecord = $this->getRecord(Logger::DEBUG);
+
+ $data = $record->getSlackData($errorLoggerRecord);
+ $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']);
+
+ $data = $record->getSlackData($emergencyLoggerRecord);
+ $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']);
+
+ $data = $record->getSlackData($warningLoggerRecord);
+ $this->assertSame(SlackRecord::COLOR_WARNING, $data['attachments'][0]['color']);
+
+ $data = $record->getSlackData($infoLoggerRecord);
+ $this->assertSame(SlackRecord::COLOR_GOOD, $data['attachments'][0]['color']);
+
+ $data = $record->getSlackData($debugLoggerRecord);
+ $this->assertSame(SlackRecord::COLOR_DEFAULT, $data['attachments'][0]['color']);
+ }
+
+ public function testAddsShortAttachmentWithoutContextAndExtra()
+ {
+ $level = Logger::ERROR;
+ $levelName = Logger::getLevelName($level);
+ $record = new SlackRecord(null, null, true, null, true);
+ $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1)));
+
+ $attachment = $data['attachments'][0];
+ $this->assertArrayHasKey('title', $attachment);
+ $this->assertArrayHasKey('fields', $attachment);
+ $this->assertSame($levelName, $attachment['title']);
+ $this->assertSame(array(), $attachment['fields']);
+ }
+
+ public function testAddsShortAttachmentWithContextAndExtra()
+ {
+ $level = Logger::ERROR;
+ $levelName = Logger::getLevelName($level);
+ $context = array('test' => 1);
+ $extra = array('tags' => array('web'));
+ $record = new SlackRecord(null, null, true, null, true, true);
+ $loggerRecord = $this->getRecord($level, 'test', $context);
+ $loggerRecord['extra'] = $extra;
+ $data = $record->getSlackData($loggerRecord);
+
+ $attachment = $data['attachments'][0];
+ $this->assertArrayHasKey('title', $attachment);
+ $this->assertArrayHasKey('fields', $attachment);
+ $this->assertCount(2, $attachment['fields']);
+ $this->assertSame($levelName, $attachment['title']);
+ $this->assertSame(
+ array(
+ array(
+ 'title' => 'Extra',
+ 'value' => sprintf('```%s```', json_encode($extra, $this->jsonPrettyPrintFlag)),
+ 'short' => false
+ ),
+ array(
+ 'title' => 'Context',
+ 'value' => sprintf('```%s```', json_encode($context, $this->jsonPrettyPrintFlag)),
+ 'short' => false
+ )
+ ),
+ $attachment['fields']
+ );
+ }
+
+ public function testAddsLongAttachmentWithoutContextAndExtra()
+ {
+ $level = Logger::ERROR;
+ $levelName = Logger::getLevelName($level);
+ $record = new SlackRecord(null, null, true, null);
+ $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1)));
+
+ $attachment = $data['attachments'][0];
+ $this->assertArrayHasKey('title', $attachment);
+ $this->assertArrayHasKey('fields', $attachment);
+ $this->assertCount(1, $attachment['fields']);
+ $this->assertSame('Message', $attachment['title']);
+ $this->assertSame(
+ array(array(
+ 'title' => 'Level',
+ 'value' => $levelName,
+ 'short' => false
+ )),
+ $attachment['fields']
+ );
+ }
+
+ public function testAddsLongAttachmentWithContextAndExtra()
+ {
+ $level = Logger::ERROR;
+ $levelName = Logger::getLevelName($level);
+ $context = array('test' => 1);
+ $extra = array('tags' => array('web'));
+ $record = new SlackRecord(null, null, true, null, false, true);
+ $loggerRecord = $this->getRecord($level, 'test', $context);
+ $loggerRecord['extra'] = $extra;
+ $data = $record->getSlackData($loggerRecord);
+
+ $expectedFields = array(
+ array(
+ 'title' => 'Level',
+ 'value' => $levelName,
+ 'short' => false,
+ ),
+ array(
+ 'title' => 'Tags',
+ 'value' => sprintf('```%s```', json_encode($extra['tags'])),
+ 'short' => false
+ ),
+ array(
+ 'title' => 'Test',
+ 'value' => $context['test'],
+ 'short' => false
+ )
+ );
+
+ $attachment = $data['attachments'][0];
+ $this->assertArrayHasKey('title', $attachment);
+ $this->assertArrayHasKey('fields', $attachment);
+ $this->assertCount(3, $attachment['fields']);
+ $this->assertSame('Message', $attachment['title']);
+ $this->assertSame(
+ $expectedFields,
+ $attachment['fields']
+ );
+ }
+
+ public function testAddsTimestampToAttachment()
+ {
+ $record = $this->getRecord();
+ $slackRecord = new SlackRecord();
+ $data = $slackRecord->getSlackData($this->getRecord());
+
+ $attachment = $data['attachments'][0];
+ $this->assertArrayHasKey('ts', $attachment);
+ $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(
+ Logger::WARNING,
+ 'test',
+ array('info' => array('library' => 'monolog', 'author' => 'Jordi'))
+ );
+ $record['extra'] = array('tags' => array('web', 'cli'));
+
+ $slackRecord = new SlackRecord(null, null, true, null, false, true, array('context.info.library', 'extra.tags.1'));
+ $data = $slackRecord->getSlackData($record);
+ $attachment = $data['attachments'][0];
+
+ $expected = array(
+ array(
+ 'title' => 'Info',
+ 'value' => sprintf('```%s```', json_encode(array('author' => 'Jordi'), $this->jsonPrettyPrintFlag)),
+ 'short' => false
+ ),
+ array(
+ 'title' => 'Tags',
+ 'value' => sprintf('```%s```', json_encode(array('web'))),
+ 'short' => false
+ ),
+ );
+
+ foreach ($expected as $field) {
+ $this->assertNotFalse(array_search($field, $attachment['fields']));
+ break;
+ }
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php
new file mode 100644
index 0000000..b12b01f
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php
@@ -0,0 +1,155 @@
+
+ *
+ * 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;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Handler\Slack\SlackRecord;
+
+/**
+ * @author Greg Kedzierski
+ * @see https://api.slack.com/
+ */
+class SlackHandlerTest extends TestCase
+{
+ /**
+ * @var resource
+ */
+ private $res;
+
+ /**
+ * @var SlackHandler
+ */
+ private $handler;
+
+ public function setUp()
+ {
+ if (!extension_loaded('openssl')) {
+ $this->markTestSkipped('This test requires openssl to run');
+ }
+ }
+
+ public function testWriteHeader()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/POST \/api\/chat.postMessage HTTP\/1.1\\r\\nHost: slack.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
+ }
+
+ public function testWriteContent()
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegExp('/username=Monolog/', $content);
+ $this->assertRegExp('/channel=channel1/', $content);
+ $this->assertRegExp('/token=myToken/', $content);
+ $this->assertRegExp('/attachments/', $content);
+ }
+
+ public function testWriteContentUsesFormatterIfProvided()
+ {
+ $this->createHandler('myToken', 'channel1', 'Monolog', false);
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->createHandler('myToken', 'channel1', 'Monolog', false);
+ $this->handler->setFormatter(new LineFormatter('foo--%message%'));
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test2'));
+ fseek($this->res, 0);
+ $content2 = fread($this->res, 1024);
+
+ $this->assertRegexp('/text=test1/', $content);
+ $this->assertRegexp('/text=foo--test2/', $content2);
+ }
+
+ public function testWriteContentWithEmoji()
+ {
+ $this->createHandler('myToken', 'channel1', 'Monolog', true, 'alien');
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/icon_emoji=%3Aalien%3A/', $content);
+ }
+
+ /**
+ * @dataProvider provideLevelColors
+ */
+ public function testWriteContentWithColors($level, $expectedColor)
+ {
+ $this->createHandler();
+ $this->handler->handle($this->getRecord($level, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/%22color%22%3A%22'.$expectedColor.'/', $content);
+ }
+
+ public function testWriteContentWithPlainTextMessage()
+ {
+ $this->createHandler('myToken', 'channel1', 'Monolog', false);
+ $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
+ fseek($this->res, 0);
+ $content = fread($this->res, 1024);
+
+ $this->assertRegexp('/text=test1/', $content);
+ }
+
+ public function provideLevelColors()
+ {
+ return array(
+ array(Logger::DEBUG, urlencode(SlackRecord::COLOR_DEFAULT)),
+ array(Logger::INFO, SlackRecord::COLOR_GOOD),
+ array(Logger::NOTICE, SlackRecord::COLOR_GOOD),
+ array(Logger::WARNING, SlackRecord::COLOR_WARNING),
+ array(Logger::ERROR, SlackRecord::COLOR_DANGER),
+ array(Logger::CRITICAL, SlackRecord::COLOR_DANGER),
+ array(Logger::ALERT, SlackRecord::COLOR_DANGER),
+ array(Logger::EMERGENCY,SlackRecord::COLOR_DANGER),
+ );
+ }
+
+ private function createHandler($token = 'myToken', $channel = 'channel1', $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeExtra = false)
+ {
+ $constructorArgs = array($token, $channel, $username, $useAttachment, $iconEmoji, Logger::DEBUG, true, $useShortAttachment, $includeExtra);
+ $this->res = fopen('php://memory', 'a');
+ $this->handler = $this->getMock(
+ '\Monolog\Handler\SlackHandler',
+ array('fsockopen', 'streamSetTimeout', 'closeSocket'),
+ $constructorArgs
+ );
+
+ $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->res));
+ $this->handler->expects($this->any())
+ ->method('streamSetTimeout')
+ ->will($this->returnValue(true));
+ $this->handler->expects($this->any())
+ ->method('closeSocket')
+ ->will($this->returnValue(true));
+
+ $this->handler->setFormatter($this->getIdentityFormatter());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php
new file mode 100644
index 0000000..c9229e2
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php
@@ -0,0 +1,107 @@
+
+ *
+ * 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;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Handler\Slack\SlackRecord;
+
+/**
+ * @author Haralan Dobrev
+ * @see https://api.slack.com/incoming-webhooks
+ * @coversDefaultClass Monolog\Handler\SlackWebhookHandler
+ */
+class SlackWebhookHandlerTest extends TestCase
+{
+ const WEBHOOK_URL = 'https://hooks.slack.com/services/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E';
+
+ /**
+ * @covers ::__construct
+ * @covers ::getSlackRecord
+ */
+ public function testConstructorMinimal()
+ {
+ $handler = new SlackWebhookHandler(self::WEBHOOK_URL);
+ $record = $this->getRecord();
+ $slackRecord = $handler->getSlackRecord();
+ $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord);
+ $this->assertEquals(array(
+ 'attachments' => array(
+ array(
+ 'fallback' => 'test',
+ 'text' => 'test',
+ 'color' => SlackRecord::COLOR_WARNING,
+ 'fields' => array(
+ array(
+ 'title' => 'Level',
+ 'value' => 'WARNING',
+ 'short' => false,
+ ),
+ ),
+ 'title' => 'Message',
+ 'mrkdwn_in' => array('fields'),
+ 'ts' => $record['datetime']->getTimestamp(),
+ ),
+ ),
+ ), $slackRecord->getSlackData($record));
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::getSlackRecord
+ */
+ public function testConstructorFull()
+ {
+ $handler = new SlackWebhookHandler(
+ self::WEBHOOK_URL,
+ 'test-channel',
+ 'test-username',
+ false,
+ ':ghost:',
+ false,
+ false,
+ Logger::DEBUG,
+ false
+ );
+
+ $slackRecord = $handler->getSlackRecord();
+ $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord);
+ $this->assertEquals(array(
+ 'username' => 'test-username',
+ 'text' => 'test',
+ 'channel' => 'test-channel',
+ 'icon_emoji' => ':ghost:',
+ ), $slackRecord->getSlackData($this->getRecord()));
+ }
+
+ /**
+ * @covers ::getFormatter
+ */
+ public function testGetFormatter()
+ {
+ $handler = new SlackWebhookHandler(self::WEBHOOK_URL);
+ $formatter = $handler->getFormatter();
+ $this->assertInstanceOf('Monolog\Formatter\FormatterInterface', $formatter);
+ }
+
+ /**
+ * @covers ::setFormatter
+ */
+ public function testSetFormatter()
+ {
+ $handler = new SlackWebhookHandler(self::WEBHOOK_URL);
+ $formatter = new LineFormatter();
+ $handler->setFormatter($formatter);
+ $this->assertSame($formatter, $handler->getFormatter());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php
new file mode 100644
index 0000000..b1b02bd
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+/**
+ * @author Haralan Dobrev
+ * @see https://slack.com/apps/A0F81R8ET-slackbot
+ * @coversDefaultClass Monolog\Handler\SlackbotHandler
+ */
+class SlackbotHandlerTest extends TestCase
+{
+ /**
+ * @covers ::__construct
+ */
+ public function testConstructorMinimal()
+ {
+ $handler = new SlackbotHandler('test-team', 'test-token', 'test-channel');
+ $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler);
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testConstructorFull()
+ {
+ $handler = new SlackbotHandler(
+ 'test-team',
+ 'test-token',
+ 'test-channel',
+ Logger::DEBUG,
+ false
+ );
+ $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php
new file mode 100644
index 0000000..1da987c
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php
@@ -0,0 +1,335 @@
+
+ *
+ * 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 Pablo de Leon Belloc
+ */
+class SocketHandlerTest extends TestCase
+{
+ /**
+ * @var Monolog\Handler\SocketHandler
+ */
+ private $handler;
+
+ /**
+ * @var resource
+ */
+ private $res;
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testInvalidHostname()
+ {
+ $this->createHandler('garbage://here');
+ $this->writeRecord('data');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testBadConnectionTimeout()
+ {
+ $this->createHandler('localhost:1234');
+ $this->handler->setConnectionTimeout(-1);
+ }
+
+ public function testSetConnectionTimeout()
+ {
+ $this->createHandler('localhost:1234');
+ $this->handler->setConnectionTimeout(10.1);
+ $this->assertEquals(10.1, $this->handler->getConnectionTimeout());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testBadTimeout()
+ {
+ $this->createHandler('localhost:1234');
+ $this->handler->setTimeout(-1);
+ }
+
+ public function testSetTimeout()
+ {
+ $this->createHandler('localhost:1234');
+ $this->handler->setTimeout(10.25);
+ $this->assertEquals(10.25, $this->handler->getTimeout());
+ }
+
+ public function testSetWritingTimeout()
+ {
+ $this->createHandler('localhost:1234');
+ $this->handler->setWritingTimeout(10.25);
+ $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');
+ $this->assertEquals('tcp://localhost:9090', $this->handler->getConnectionString());
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testExceptionIsThrownOnFsockopenError()
+ {
+ $this->setMockHandler(array('fsockopen'));
+ $this->handler->expects($this->once())
+ ->method('fsockopen')
+ ->will($this->returnValue(false));
+ $this->writeRecord('Hello world');
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testExceptionIsThrownOnPfsockopenError()
+ {
+ $this->setMockHandler(array('pfsockopen'));
+ $this->handler->expects($this->once())
+ ->method('pfsockopen')
+ ->will($this->returnValue(false));
+ $this->handler->setPersistent(true);
+ $this->writeRecord('Hello world');
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testExceptionIsThrownIfCannotSetTimeout()
+ {
+ $this->setMockHandler(array('streamSetTimeout'));
+ $this->handler->expects($this->once())
+ ->method('streamSetTimeout')
+ ->will($this->returnValue(false));
+ $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
+ */
+ public function testWriteFailsOnIfFwriteReturnsFalse()
+ {
+ $this->setMockHandler(array('fwrite'));
+
+ $callback = function ($arg) {
+ $map = array(
+ 'Hello world' => 6,
+ 'world' => false,
+ );
+
+ return $map[$arg];
+ };
+
+ $this->handler->expects($this->exactly(2))
+ ->method('fwrite')
+ ->will($this->returnCallback($callback));
+
+ $this->writeRecord('Hello world');
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testWriteFailsIfStreamTimesOut()
+ {
+ $this->setMockHandler(array('fwrite', 'streamGetMetadata'));
+
+ $callback = function ($arg) {
+ $map = array(
+ 'Hello world' => 6,
+ 'world' => 5,
+ );
+
+ return $map[$arg];
+ };
+
+ $this->handler->expects($this->exactly(1))
+ ->method('fwrite')
+ ->will($this->returnCallback($callback));
+ $this->handler->expects($this->exactly(1))
+ ->method('streamGetMetadata')
+ ->will($this->returnValue(array('timed_out' => true)));
+
+ $this->writeRecord('Hello world');
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testWriteFailsOnIncompleteWrite()
+ {
+ $this->setMockHandler(array('fwrite', 'streamGetMetadata'));
+
+ $res = $this->res;
+ $callback = function ($string) use ($res) {
+ fclose($res);
+
+ return strlen('Hello');
+ };
+
+ $this->handler->expects($this->exactly(1))
+ ->method('fwrite')
+ ->will($this->returnCallback($callback));
+ $this->handler->expects($this->exactly(1))
+ ->method('streamGetMetadata')
+ ->will($this->returnValue(array('timed_out' => false)));
+
+ $this->writeRecord('Hello world');
+ }
+
+ public function testWriteWithMemoryFile()
+ {
+ $this->setMockHandler();
+ $this->writeRecord('test1');
+ $this->writeRecord('test2');
+ $this->writeRecord('test3');
+ fseek($this->res, 0);
+ $this->assertEquals('test1test2test3', fread($this->res, 1024));
+ }
+
+ public function testWriteWithMock()
+ {
+ $this->setMockHandler(array('fwrite'));
+
+ $callback = function ($arg) {
+ $map = array(
+ 'Hello world' => 6,
+ 'world' => 5,
+ );
+
+ return $map[$arg];
+ };
+
+ $this->handler->expects($this->exactly(2))
+ ->method('fwrite')
+ ->will($this->returnCallback($callback));
+
+ $this->writeRecord('Hello world');
+ }
+
+ public function testClose()
+ {
+ $this->setMockHandler();
+ $this->writeRecord('Hello world');
+ $this->assertInternalType('resource', $this->res);
+ $this->handler->close();
+ $this->assertFalse(is_resource($this->res), "Expected resource to be closed after closing handler");
+ }
+
+ public function testCloseDoesNotClosePersistentSocket()
+ {
+ $this->setMockHandler();
+ $this->handler->setPersistent(true);
+ $this->writeRecord('Hello world');
+ $this->assertTrue(is_resource($this->res));
+ $this->handler->close();
+ $this->assertTrue(is_resource($this->res));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testAvoidInfiniteLoopWhenNoDataIsWrittenForAWritingTimeoutSeconds()
+ {
+ $this->setMockHandler(array('fwrite', 'streamGetMetadata'));
+
+ $this->handler->expects($this->any())
+ ->method('fwrite')
+ ->will($this->returnValue(0));
+
+ $this->handler->expects($this->any())
+ ->method('streamGetMetadata')
+ ->will($this->returnValue(array('timed_out' => false)));
+
+ $this->handler->setWritingTimeout(1);
+
+ $this->writeRecord('Hello world');
+ }
+
+ private function createHandler($connectionString)
+ {
+ $this->handler = new SocketHandler($connectionString);
+ $this->handler->setFormatter($this->getIdentityFormatter());
+ }
+
+ private function writeRecord($string)
+ {
+ $this->handler->handle($this->getRecord(Logger::WARNING, $string));
+ }
+
+ private function setMockHandler(array $methods = array())
+ {
+ $this->res = fopen('php://memory', 'a');
+
+ $defaultMethods = array('fsockopen', 'pfsockopen', 'streamSetTimeout');
+ $newMethods = array_diff($methods, $defaultMethods);
+
+ $finalMethods = array_merge($defaultMethods, $newMethods);
+
+ $this->handler = $this->getMock(
+ '\Monolog\Handler\SocketHandler', $finalMethods, array('localhost:1234')
+ );
+
+ if (!in_array('fsockopen', $methods)) {
+ $this->handler->expects($this->any())
+ ->method('fsockopen')
+ ->will($this->returnValue($this->res));
+ }
+
+ if (!in_array('pfsockopen', $methods)) {
+ $this->handler->expects($this->any())
+ ->method('pfsockopen')
+ ->will($this->returnValue($this->res));
+ }
+
+ if (!in_array('streamSetTimeout', $methods)) {
+ $this->handler->expects($this->any())
+ ->method('streamSetTimeout')
+ ->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/core/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php
new file mode 100644
index 0000000..487030f
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php
@@ -0,0 +1,184 @@
+
+ *
+ * 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;
+
+class StreamHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWrite()
+ {
+ $handle = fopen('php://memory', 'a+');
+ $handler = new StreamHandler($handle);
+ $handler->setFormatter($this->getIdentityFormatter());
+ $handler->handle($this->getRecord(Logger::WARNING, 'test'));
+ $handler->handle($this->getRecord(Logger::WARNING, 'test2'));
+ $handler->handle($this->getRecord(Logger::WARNING, 'test3'));
+ fseek($handle, 0);
+ $this->assertEquals('testtest2test3', fread($handle, 100));
+ }
+
+ /**
+ * @covers Monolog\Handler\StreamHandler::close
+ */
+ public function testCloseKeepsExternalHandlersOpen()
+ {
+ $handle = fopen('php://memory', 'a+');
+ $handler = new StreamHandler($handle);
+ $this->assertTrue(is_resource($handle));
+ $handler->close();
+ $this->assertTrue(is_resource($handle));
+ }
+
+ /**
+ * @covers Monolog\Handler\StreamHandler::close
+ */
+ public function testClose()
+ {
+ $handler = new StreamHandler('php://memory');
+ $handler->handle($this->getRecord(Logger::WARNING, 'test'));
+ $streamProp = new \ReflectionProperty('Monolog\Handler\StreamHandler', 'stream');
+ $streamProp->setAccessible(true);
+ $handle = $streamProp->getValue($handler);
+
+ $this->assertTrue(is_resource($handle));
+ $handler->close();
+ $this->assertFalse(is_resource($handle));
+ }
+
+ /**
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteCreatesTheStreamResource()
+ {
+ $handler = new StreamHandler('php://memory');
+ $handler->handle($this->getRecord());
+ }
+
+ /**
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteLocking()
+ {
+ $temp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'monolog_locked_log';
+ $handler = new StreamHandler($temp, Logger::DEBUG, true, null, true);
+ $handler->handle($this->getRecord());
+ }
+
+ /**
+ * @expectedException LogicException
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteMissingResource()
+ {
+ $handler = new StreamHandler(null);
+ $handler->handle($this->getRecord());
+ }
+
+ public function invalidArgumentProvider()
+ {
+ return array(
+ array(1),
+ array(array()),
+ array(array('bogus://url')),
+ );
+ }
+
+ /**
+ * @dataProvider invalidArgumentProvider
+ * @expectedException InvalidArgumentException
+ * @covers Monolog\Handler\StreamHandler::__construct
+ */
+ public function testWriteInvalidArgument($invalidArgument)
+ {
+ $handler = new StreamHandler($invalidArgument);
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteInvalidResource()
+ {
+ $handler = new StreamHandler('bogus://url');
+ $handler->handle($this->getRecord());
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteNonExistingResource()
+ {
+ $handler = new StreamHandler('ftp://foo/bar/baz/'.rand(0, 10000));
+ $handler->handle($this->getRecord());
+ }
+
+ /**
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteNonExistingPath()
+ {
+ $handler = new StreamHandler(sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000));
+ $handler->handle($this->getRecord());
+ }
+
+ /**
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteNonExistingFileResource()
+ {
+ $handler = new StreamHandler('file://'.sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000));
+ $handler->handle($this->getRecord());
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessageRegExp /There is no existing directory at/
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteNonExistingAndNotCreatablePath()
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $this->markTestSkipped('Permissions checks can not run on windows');
+ }
+ $handler = new StreamHandler('/foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000));
+ $handler->handle($this->getRecord());
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessageRegExp /There is no existing directory at/
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteNonExistingAndNotCreatableFileResource()
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $this->markTestSkipped('Permissions checks can not run on windows');
+ }
+ $handler = new StreamHandler('file:///foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000));
+ $handler->handle($this->getRecord());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php
new file mode 100644
index 0000000..1d62940
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+use Monolog\TestCase;
+
+class SwiftMailerHandlerTest extends TestCase
+{
+ /** @var \Swift_Mailer|\PHPUnit_Framework_MockObject_MockObject */
+ private $mailer;
+
+ public function setUp()
+ {
+ $this->mailer = $this
+ ->getMockBuilder('Swift_Mailer')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function testMessageCreationIsLazyWhenUsingCallback()
+ {
+ $this->mailer->expects($this->never())
+ ->method('send');
+
+ $callback = function () {
+ throw new \RuntimeException('Swift_Message creation callback should not have been called in this test');
+ };
+ $handler = new SwiftMailerHandler($this->mailer, $callback);
+
+ $records = array(
+ $this->getRecord(Logger::DEBUG),
+ $this->getRecord(Logger::INFO),
+ );
+ $handler->handleBatch($records);
+ }
+
+ public function testMessageCanBeCustomizedGivenLoggedData()
+ {
+ // Wire Mailer to expect a specific Swift_Message with a customized Subject
+ $expectedMessage = new \Swift_Message();
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->with($this->callback(function ($value) use ($expectedMessage) {
+ return $value instanceof \Swift_Message
+ && $value->getSubject() === 'Emergency'
+ && $value === $expectedMessage;
+ }));
+
+ // Callback dynamically changes subject based on number of logged records
+ $callback = function ($content, array $records) use ($expectedMessage) {
+ $subject = count($records) > 0 ? 'Emergency' : 'Normal';
+ $expectedMessage->setSubject($subject);
+
+ return $expectedMessage;
+ };
+ $handler = new SwiftMailerHandler($this->mailer, $callback);
+
+ // Logging 1 record makes this an Emergency
+ $records = array(
+ $this->getRecord(Logger::EMERGENCY),
+ );
+ $handler->handleBatch($records);
+ }
+
+ public function testMessageSubjectFormatting()
+ {
+ // Wire Mailer to expect a specific Swift_Message with a customized Subject
+ $messageTemplate = new \Swift_Message();
+ $messageTemplate->setSubject('Alert: %level_name% %message%');
+ $receivedMessage = null;
+
+ $this->mailer->expects($this->once())
+ ->method('send')
+ ->with($this->callback(function ($value) use (&$receivedMessage) {
+ $receivedMessage = $value;
+ return true;
+ }));
+
+ $handler = new SwiftMailerHandler($this->mailer, $messageTemplate);
+
+ $records = array(
+ $this->getRecord(Logger::EMERGENCY),
+ );
+ $handler->handleBatch($records);
+
+ $this->assertEquals('Alert: EMERGENCY test', $receivedMessage->getSubject());
+ }
+
+ public function testMessageHaveUniqueId()
+ {
+ $messageTemplate = new \Swift_Message();
+ $handler = new SwiftMailerHandler($this->mailer, $messageTemplate);
+
+ $method = new \ReflectionMethod('Monolog\Handler\SwiftMailerHandler', 'buildMessage');
+ $method->setAccessible(true);
+ $method->invokeArgs($handler, array($messageTemplate, array()));
+
+ $builtMessage1 = $method->invoke($handler, $messageTemplate, array());
+ $builtMessage2 = $method->invoke($handler, $messageTemplate, array());
+
+ $this->assertFalse($builtMessage1->getId() === $builtMessage2->getId(), 'Two different messages have the same id');
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php
new file mode 100644
index 0000000..8f9e46b
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+
+class SyslogHandlerTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Monolog\Handler\SyslogHandler::__construct
+ */
+ public function testConstruct()
+ {
+ $handler = new SyslogHandler('test');
+ $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
+
+ $handler = new SyslogHandler('test', LOG_USER);
+ $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
+
+ $handler = new SyslogHandler('test', 'user');
+ $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
+
+ $handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR);
+ $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
+ }
+
+ /**
+ * @covers Monolog\Handler\SyslogHandler::__construct
+ */
+ public function testConstructInvalidFacility()
+ {
+ $this->setExpectedException('UnexpectedValueException');
+ $handler = new SyslogHandler('test', 'unknown');
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php
new file mode 100644
index 0000000..7ee8a98
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+
+/**
+ * @requires extension sockets
+ */
+class SyslogUdpHandlerTest extends TestCase
+{
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testWeValidateFacilities()
+ {
+ $handler = new SyslogUdpHandler("ip", null, "invalidFacility");
+ }
+
+ public function testWeSplitIntoLines()
+ {
+ $time = '2014-01-07T12:34';
+ $pid = getmypid();
+ $host = gethostname();
+
+ $handler = $this->getMockBuilder('\Monolog\Handler\SyslogUdpHandler')
+ ->setConstructorArgs(array("127.0.0.1", 514, "authpriv"))
+ ->setMethods(array('getDateTime'))
+ ->getMock();
+
+ $handler->method('getDateTime')
+ ->willReturn($time);
+
+ $handler->setFormatter(new \Monolog\Formatter\ChromePHPFormatter());
+
+ $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol'));
+ $socket->expects($this->at(0))
+ ->method('write')
+ ->with("lol", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 $time $host php $pid - - ");
+ $socket->expects($this->at(1))
+ ->method('write')
+ ->with("hej", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 $time $host php $pid - - ");
+
+ $handler->setSocket($socket);
+
+ $handler->handle($this->getRecordWithMessage("hej\nlol"));
+ }
+
+ public function testSplitWorksOnEmptyMsg()
+ {
+ $handler = new SyslogUdpHandler("127.0.0.1", 514, "authpriv");
+ $handler->setFormatter($this->getIdentityFormatter());
+
+ $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol'));
+ $socket->expects($this->never())
+ ->method('write');
+
+ $handler->setSocket($socket);
+
+ $handler->handle($this->getRecordWithMessage(null));
+ }
+
+ protected function getRecordWithMessage($msg)
+ {
+ return array('message' => $msg, 'level' => \Monolog\Logger::WARNING, 'context' => null, 'extra' => array(), 'channel' => 'lol');
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php
new file mode 100644
index 0000000..a7c4fc9
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php
@@ -0,0 +1,116 @@
+
+ *
+ * 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;
+
+/**
+ * @covers Monolog\Handler\TestHandler
+ */
+class TestHandlerTest extends TestCase
+{
+ /**
+ * @dataProvider methodProvider
+ */
+ public function testHandler($method, $level)
+ {
+ $handler = new TestHandler;
+ $record = $this->getRecord($level, 'test'.$method);
+ $this->assertFalse($handler->hasRecords($level));
+ $this->assertFalse($handler->hasRecord($record, $level));
+ $this->assertFalse($handler->{'has'.$method}($record), 'has'.$method);
+ $this->assertFalse($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains');
+ $this->assertFalse($handler->{'has'.$method.'ThatPasses'}(function ($rec) {
+ return true;
+ }), 'has'.$method.'ThatPasses');
+ $this->assertFalse($handler->{'has'.$method.'ThatMatches'}('/test\w+/'));
+ $this->assertFalse($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records');
+ $handler->handle($record);
+
+ $this->assertFalse($handler->{'has'.$method}('bar'), 'has'.$method);
+ $this->assertTrue($handler->hasRecords($level));
+ $this->assertTrue($handler->hasRecord($record, $level));
+ $this->assertTrue($handler->{'has'.$method}($record), 'has'.$method);
+ $this->assertTrue($handler->{'has'.$method}('test'.$method), 'has'.$method);
+ $this->assertTrue($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains');
+ $this->assertTrue($handler->{'has'.$method.'ThatPasses'}(function ($rec) {
+ return true;
+ }), 'has'.$method.'ThatPasses');
+ $this->assertTrue($handler->{'has'.$method.'ThatMatches'}('/test\w+/'));
+ $this->assertTrue($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records');
+
+ $records = $handler->getRecords();
+ unset($records[0]['formatted']);
+ $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(
+ array('Emergency', Logger::EMERGENCY),
+ array('Alert' , Logger::ALERT),
+ array('Critical' , Logger::CRITICAL),
+ array('Error' , Logger::ERROR),
+ array('Warning' , Logger::WARNING),
+ array('Info' , Logger::INFO),
+ array('Notice' , Logger::NOTICE),
+ array('Debug' , Logger::DEBUG),
+ );
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php
new file mode 100644
index 0000000..fa524d0
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php
@@ -0,0 +1,64 @@
+
+ *
+ * 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\Handler\SyslogUdp\UdpSocket;
+
+/**
+ * @requires extension sockets
+ */
+class UdpSocketTest extends TestCase
+{
+ public function testWeDoNotTruncateShortMessages()
+ {
+ $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol'));
+
+ $socket->expects($this->at(0))
+ ->method('send')
+ ->with("HEADER: The quick brown fox jumps over the lazy dog");
+
+ $socket->write("The quick brown fox jumps over the lazy dog", "HEADER: ");
+ }
+
+ public function testLongMessagesAreTruncated()
+ {
+ $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol'));
+
+ $truncatedString = str_repeat("derp", 16254).'d';
+
+ $socket->expects($this->exactly(1))
+ ->method('send')
+ ->with("HEADER" . $truncatedString);
+
+ $longString = str_repeat("derp", 20000);
+
+ $socket->write($longString, "HEADER");
+ }
+
+ public function testDoubleCloseDoesNotError()
+ {
+ $socket = new UdpSocket('127.0.0.1', 514);
+ $socket->close();
+ $socket->close();
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testWriteAfterCloseErrors()
+ {
+ $socket = new UdpSocket('127.0.0.1', 514);
+ $socket->close();
+ $socket->write('foo', "HEADER");
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php
new file mode 100644
index 0000000..0594a23
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+use Monolog\Logger;
+
+class WhatFailureGroupHandlerTest extends TestCase
+{
+ /**
+ * @covers Monolog\Handler\WhatFailureGroupHandler::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorOnlyTakesHandler()
+ {
+ new WhatFailureGroupHandler(array(new TestHandler(), "foo"));
+ }
+
+ /**
+ * @covers Monolog\Handler\WhatFailureGroupHandler::__construct
+ * @covers Monolog\Handler\WhatFailureGroupHandler::handle
+ */
+ public function testHandle()
+ {
+ $testHandlers = array(new TestHandler(), new TestHandler());
+ $handler = new WhatFailureGroupHandler($testHandlers);
+ $handler->handle($this->getRecord(Logger::DEBUG));
+ $handler->handle($this->getRecord(Logger::INFO));
+ foreach ($testHandlers as $test) {
+ $this->assertTrue($test->hasDebugRecords());
+ $this->assertTrue($test->hasInfoRecords());
+ $this->assertTrue(count($test->getRecords()) === 2);
+ }
+ }
+
+ /**
+ * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch
+ */
+ public function testHandleBatch()
+ {
+ $testHandlers = array(new TestHandler(), new TestHandler());
+ $handler = new WhatFailureGroupHandler($testHandlers);
+ $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);
+ }
+ }
+
+ /**
+ * @covers Monolog\Handler\WhatFailureGroupHandler::isHandling
+ */
+ public function testIsHandling()
+ {
+ $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING));
+ $handler = new WhatFailureGroupHandler($testHandlers);
+ $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR)));
+ $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING)));
+ $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG)));
+ }
+
+ /**
+ * @covers Monolog\Handler\WhatFailureGroupHandler::handle
+ */
+ public function testHandleUsesProcessors()
+ {
+ $test = new TestHandler();
+ $handler = new WhatFailureGroupHandler(array($test));
+ $handler->pushProcessor(function ($record) {
+ $record['extra']['foo'] = true;
+
+ return $record;
+ });
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasWarningRecords());
+ $records = $test->getRecords();
+ $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
+ */
+ public function testHandleException()
+ {
+ $test = new TestHandler();
+ $exception = new ExceptionTestHandler();
+ $handler = new WhatFailureGroupHandler(array($exception, $test, $exception));
+ $handler->pushProcessor(function ($record) {
+ $record['extra']['foo'] = true;
+
+ return $record;
+ });
+ $handler->handle($this->getRecord(Logger::WARNING));
+ $this->assertTrue($test->hasWarningRecords());
+ $records = $test->getRecords();
+ $this->assertTrue($records[0]['extra']['foo']);
+ }
+}
+
+class ExceptionTestHandler extends TestHandler
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function handle(array $record)
+ {
+ parent::handle($record);
+
+ throw new \Exception("ExceptionTestHandler::handle");
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php
new file mode 100644
index 0000000..69b001e
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\TestCase;
+
+class ZendMonitorHandlerTest extends TestCase
+{
+ protected $zendMonitorHandler;
+
+ public function setUp()
+ {
+ if (!function_exists('zend_monitor_custom_event')) {
+ $this->markTestSkipped('ZendServer is not installed');
+ }
+ }
+
+ /**
+ * @covers Monolog\Handler\ZendMonitorHandler::write
+ */
+ public function testWrite()
+ {
+ $record = $this->getRecord();
+ $formatterResult = array(
+ 'message' => $record['message'],
+ );
+
+ $zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler')
+ ->setMethods(array('writeZendMonitorCustomEvent', 'getDefaultFormatter'))
+ ->getMock();
+
+ $formatterMock = $this->getMockBuilder('Monolog\Formatter\NormalizerFormatter')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $formatterMock->expects($this->once())
+ ->method('format')
+ ->will($this->returnValue($formatterResult));
+
+ $zendMonitor->expects($this->once())
+ ->method('getDefaultFormatter')
+ ->will($this->returnValue($formatterMock));
+
+ $levelMap = $zendMonitor->getLevelMap();
+
+ $zendMonitor->expects($this->once())
+ ->method('writeZendMonitorCustomEvent')
+ ->with($levelMap[$record['level']], $record['message'], $formatterResult);
+
+ $zendMonitor->handle($record);
+ }
+
+ /**
+ * @covers Monolog\Handler\ZendMonitorHandler::getDefaultFormatter
+ */
+ public function testGetDefaultFormatterReturnsNormalizerFormatter()
+ {
+ $zendMonitor = new ZendMonitorHandler();
+ $this->assertInstanceOf('Monolog\Formatter\NormalizerFormatter', $zendMonitor->getDefaultFormatter());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/LoggerTest.php b/core/vendor/monolog/monolog/tests/Monolog/LoggerTest.php
new file mode 100644
index 0000000..442e87d
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/LoggerTest.php
@@ -0,0 +1,690 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+use Monolog\Processor\WebProcessor;
+use Monolog\Handler\TestHandler;
+
+class LoggerTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Monolog\Logger::getName
+ */
+ public function testGetName()
+ {
+ $logger = new Logger('foo');
+ $this->assertEquals('foo', $logger->getName());
+ }
+
+ /**
+ * @covers Monolog\Logger::getLevelName
+ */
+ public function testGetLevelName()
+ {
+ $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR));
+ }
+
+ /**
+ * @covers Monolog\Logger::withName
+ */
+ public function testWithName()
+ {
+ $first = new Logger('first', array($handler = new TestHandler()));
+ $second = $first->withName('second');
+
+ $this->assertSame('first', $first->getName());
+ $this->assertSame('second', $second->getName());
+ $this->assertSame($handler, $second->popHandler());
+ }
+
+ /**
+ * @covers Monolog\Logger::toMonologLevel
+ */
+ public function testConvertPSR3ToMonologLevel()
+ {
+ $this->assertEquals(Logger::toMonologLevel('debug'), 100);
+ $this->assertEquals(Logger::toMonologLevel('info'), 200);
+ $this->assertEquals(Logger::toMonologLevel('notice'), 250);
+ $this->assertEquals(Logger::toMonologLevel('warning'), 300);
+ $this->assertEquals(Logger::toMonologLevel('error'), 400);
+ $this->assertEquals(Logger::toMonologLevel('critical'), 500);
+ $this->assertEquals(Logger::toMonologLevel('alert'), 550);
+ $this->assertEquals(Logger::toMonologLevel('emergency'), 600);
+ }
+
+ /**
+ * @covers Monolog\Logger::getLevelName
+ * @expectedException InvalidArgumentException
+ */
+ public function testGetLevelNameThrows()
+ {
+ Logger::getLevelName(5);
+ }
+
+ /**
+ * @covers Monolog\Logger::__construct
+ */
+ public function testChannel()
+ {
+ $logger = new Logger('foo');
+ $handler = new TestHandler;
+ $logger->pushHandler($handler);
+ $logger->addWarning('test');
+ list($record) = $handler->getRecords();
+ $this->assertEquals('foo', $record['channel']);
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testLog()
+ {
+ $logger = new Logger(__METHOD__);
+
+ $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'));
+ $handler->expects($this->once())
+ ->method('handle');
+ $logger->pushHandler($handler);
+
+ $this->assertTrue($logger->addWarning('test'));
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testLogNotHandled()
+ {
+ $logger = new Logger(__METHOD__);
+
+ $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'), array(Logger::ERROR));
+ $handler->expects($this->never())
+ ->method('handle');
+ $logger->pushHandler($handler);
+
+ $this->assertFalse($logger->addWarning('test'));
+ }
+
+ public function testHandlersInCtor()
+ {
+ $handler1 = new TestHandler;
+ $handler2 = new TestHandler;
+ $logger = new Logger(__METHOD__, array($handler1, $handler2));
+
+ $this->assertEquals($handler1, $logger->popHandler());
+ $this->assertEquals($handler2, $logger->popHandler());
+ }
+
+ public function testProcessorsInCtor()
+ {
+ $processor1 = new WebProcessor;
+ $processor2 = new WebProcessor;
+ $logger = new Logger(__METHOD__, array(), array($processor1, $processor2));
+
+ $this->assertEquals($processor1, $logger->popProcessor());
+ $this->assertEquals($processor2, $logger->popProcessor());
+ }
+
+ /**
+ * @covers Monolog\Logger::pushHandler
+ * @covers Monolog\Logger::popHandler
+ * @expectedException LogicException
+ */
+ public function testPushPopHandler()
+ {
+ $logger = new Logger(__METHOD__);
+ $handler1 = new TestHandler;
+ $handler2 = new TestHandler;
+
+ $logger->pushHandler($handler1);
+ $logger->pushHandler($handler2);
+
+ $this->assertEquals($handler2, $logger->popHandler());
+ $this->assertEquals($handler1, $logger->popHandler());
+ $logger->popHandler();
+ }
+
+ /**
+ * @covers Monolog\Logger::setHandlers
+ */
+ public function testSetHandlers()
+ {
+ $logger = new Logger(__METHOD__);
+ $handler1 = new TestHandler;
+ $handler2 = new TestHandler;
+
+ $logger->pushHandler($handler1);
+ $logger->setHandlers(array($handler2));
+
+ // handler1 has been removed
+ $this->assertEquals(array($handler2), $logger->getHandlers());
+
+ $logger->setHandlers(array(
+ "AMapKey" => $handler1,
+ "Woop" => $handler2,
+ ));
+
+ // Keys have been scrubbed
+ $this->assertEquals(array($handler1, $handler2), $logger->getHandlers());
+ }
+
+ /**
+ * @covers Monolog\Logger::pushProcessor
+ * @covers Monolog\Logger::popProcessor
+ * @expectedException LogicException
+ */
+ public function testPushPopProcessor()
+ {
+ $logger = new Logger(__METHOD__);
+ $processor1 = new WebProcessor;
+ $processor2 = new WebProcessor;
+
+ $logger->pushProcessor($processor1);
+ $logger->pushProcessor($processor2);
+
+ $this->assertEquals($processor2, $logger->popProcessor());
+ $this->assertEquals($processor1, $logger->popProcessor());
+ $logger->popProcessor();
+ }
+
+ /**
+ * @covers Monolog\Logger::pushProcessor
+ * @expectedException InvalidArgumentException
+ */
+ public function testPushProcessorWithNonCallable()
+ {
+ $logger = new Logger(__METHOD__);
+
+ $logger->pushProcessor(new \stdClass());
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testProcessorsAreExecuted()
+ {
+ $logger = new Logger(__METHOD__);
+ $handler = new TestHandler;
+ $logger->pushHandler($handler);
+ $logger->pushProcessor(function ($record) {
+ $record['extra']['win'] = true;
+
+ return $record;
+ });
+ $logger->addError('test');
+ list($record) = $handler->getRecords();
+ $this->assertTrue($record['extra']['win']);
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testProcessorsAreCalledOnlyOnce()
+ {
+ $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->returnValue(true))
+ ;
+ $logger->pushHandler($handler);
+
+ $processor = $this->getMockBuilder('Monolog\Processor\WebProcessor')
+ ->disableOriginalConstructor()
+ ->setMethods(array('__invoke'))
+ ->getMock()
+ ;
+ $processor->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnArgument(0))
+ ;
+ $logger->pushProcessor($processor);
+
+ $logger->addError('test');
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testProcessorsNotCalledWhenNotHandled()
+ {
+ $logger = new Logger(__METHOD__);
+ $handler = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler->expects($this->once())
+ ->method('isHandling')
+ ->will($this->returnValue(false))
+ ;
+ $logger->pushHandler($handler);
+ $that = $this;
+ $logger->pushProcessor(function ($record) use ($that) {
+ $that->fail('The processor should not be called');
+ });
+ $logger->addAlert('test');
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testHandlersNotCalledBeforeFirstHandling()
+ {
+ $logger = new Logger(__METHOD__);
+
+ $handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler1->expects($this->never())
+ ->method('isHandling')
+ ->will($this->returnValue(false))
+ ;
+ $handler1->expects($this->once())
+ ->method('handle')
+ ->will($this->returnValue(false))
+ ;
+ $logger->pushHandler($handler1);
+
+ $handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler2->expects($this->once())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+ $handler2->expects($this->once())
+ ->method('handle')
+ ->will($this->returnValue(false))
+ ;
+ $logger->pushHandler($handler2);
+
+ $handler3 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler3->expects($this->once())
+ ->method('isHandling')
+ ->will($this->returnValue(false))
+ ;
+ $handler3->expects($this->never())
+ ->method('handle')
+ ;
+ $logger->pushHandler($handler3);
+
+ $logger->debug('test');
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testHandlersNotCalledBeforeFirstHandlingWithAssocArray()
+ {
+ $handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler1->expects($this->never())
+ ->method('isHandling')
+ ->will($this->returnValue(false))
+ ;
+ $handler1->expects($this->once())
+ ->method('handle')
+ ->will($this->returnValue(false))
+ ;
+
+ $handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler2->expects($this->once())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+ $handler2->expects($this->once())
+ ->method('handle')
+ ->will($this->returnValue(false))
+ ;
+
+ $handler3 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler3->expects($this->once())
+ ->method('isHandling')
+ ->will($this->returnValue(false))
+ ;
+ $handler3->expects($this->never())
+ ->method('handle')
+ ;
+
+ $logger = new Logger(__METHOD__, array('last' => $handler3, 'second' => $handler2, 'first' => $handler1));
+
+ $logger->debug('test');
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testBubblingWhenTheHandlerReturnsFalse()
+ {
+ $logger = new Logger(__METHOD__);
+
+ $handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler1->expects($this->any())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+ $handler1->expects($this->once())
+ ->method('handle')
+ ->will($this->returnValue(false))
+ ;
+ $logger->pushHandler($handler1);
+
+ $handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler2->expects($this->any())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+ $handler2->expects($this->once())
+ ->method('handle')
+ ->will($this->returnValue(false))
+ ;
+ $logger->pushHandler($handler2);
+
+ $logger->debug('test');
+ }
+
+ /**
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testNotBubblingWhenTheHandlerReturnsTrue()
+ {
+ $logger = new Logger(__METHOD__);
+
+ $handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler1->expects($this->any())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+ $handler1->expects($this->never())
+ ->method('handle')
+ ;
+ $logger->pushHandler($handler1);
+
+ $handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler2->expects($this->any())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+ $handler2->expects($this->once())
+ ->method('handle')
+ ->will($this->returnValue(true))
+ ;
+ $logger->pushHandler($handler2);
+
+ $logger->debug('test');
+ }
+
+ /**
+ * @covers Monolog\Logger::isHandling
+ */
+ public function testIsHandling()
+ {
+ $logger = new Logger(__METHOD__);
+
+ $handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler1->expects($this->any())
+ ->method('isHandling')
+ ->will($this->returnValue(false))
+ ;
+
+ $logger->pushHandler($handler1);
+ $this->assertFalse($logger->isHandling(Logger::DEBUG));
+
+ $handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
+ $handler2->expects($this->any())
+ ->method('isHandling')
+ ->will($this->returnValue(true))
+ ;
+
+ $logger->pushHandler($handler2);
+ $this->assertTrue($logger->isHandling(Logger::DEBUG));
+ }
+
+ /**
+ * @dataProvider logMethodProvider
+ * @covers Monolog\Logger::addDebug
+ * @covers Monolog\Logger::addInfo
+ * @covers Monolog\Logger::addNotice
+ * @covers Monolog\Logger::addWarning
+ * @covers Monolog\Logger::addError
+ * @covers Monolog\Logger::addCritical
+ * @covers Monolog\Logger::addAlert
+ * @covers Monolog\Logger::addEmergency
+ * @covers Monolog\Logger::debug
+ * @covers Monolog\Logger::info
+ * @covers Monolog\Logger::notice
+ * @covers Monolog\Logger::warn
+ * @covers Monolog\Logger::err
+ * @covers Monolog\Logger::crit
+ * @covers Monolog\Logger::alert
+ * @covers Monolog\Logger::emerg
+ */
+ public function testLogMethods($method, $expectedLevel)
+ {
+ $logger = new Logger('foo');
+ $handler = new TestHandler;
+ $logger->pushHandler($handler);
+ $logger->{$method}('test');
+ list($record) = $handler->getRecords();
+ $this->assertEquals($expectedLevel, $record['level']);
+ }
+
+ public function logMethodProvider()
+ {
+ return array(
+ // monolog methods
+ array('addDebug', Logger::DEBUG),
+ array('addInfo', Logger::INFO),
+ array('addNotice', Logger::NOTICE),
+ array('addWarning', Logger::WARNING),
+ array('addError', Logger::ERROR),
+ array('addCritical', Logger::CRITICAL),
+ array('addAlert', Logger::ALERT),
+ array('addEmergency', Logger::EMERGENCY),
+
+ // ZF/Sf2 compat methods
+ array('debug', Logger::DEBUG),
+ array('info', Logger::INFO),
+ array('notice', Logger::NOTICE),
+ array('warn', Logger::WARNING),
+ array('err', Logger::ERROR),
+ array('crit', Logger::CRITICAL),
+ array('alert', Logger::ALERT),
+ array('emerg', Logger::EMERGENCY),
+ );
+ }
+
+ /**
+ * @dataProvider setTimezoneProvider
+ * @covers Monolog\Logger::setTimezone
+ */
+ public function testSetTimezone($tz)
+ {
+ Logger::setTimezone($tz);
+ $logger = new Logger('foo');
+ $handler = new TestHandler;
+ $logger->pushHandler($handler);
+ $logger->info('test');
+ list($record) = $handler->getRecords();
+ $this->assertEquals($tz, $record['datetime']->getTimezone());
+ }
+
+ public function setTimezoneProvider()
+ {
+ return array_map(
+ function ($tz) { return array(new \DateTimeZone($tz)); },
+ \DateTimeZone::listIdentifiers()
+ );
+ }
+
+ /**
+ * @dataProvider useMicrosecondTimestampsProvider
+ * @covers Monolog\Logger::useMicrosecondTimestamps
+ * @covers Monolog\Logger::addRecord
+ */
+ public function testUseMicrosecondTimestamps($micro, $assert)
+ {
+ $logger = new Logger('foo');
+ $logger->useMicrosecondTimestamps($micro);
+ $handler = new TestHandler;
+ $logger->pushHandler($handler);
+ $logger->info('test');
+ list($record) = $handler->getRecords();
+ $this->{$assert}('000000', $record['datetime']->format('u'));
+ }
+
+ public function useMicrosecondTimestampsProvider()
+ {
+ return array(
+ // this has a very small chance of a false negative (1/10^6)
+ 'with microseconds' => array(true, 'assertNotSame'),
+ '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/core/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php
new file mode 100644
index 0000000..5adb505
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\TestCase;
+
+class GitProcessorTest extends TestCase
+{
+ /**
+ * @covers Monolog\Processor\GitProcessor::__invoke
+ */
+ public function testProcessor()
+ {
+ $processor = new GitProcessor();
+ $record = $processor($this->getRecord());
+
+ $this->assertArrayHasKey('git', $record['extra']);
+ $this->assertTrue(!is_array($record['extra']['git']['branch']));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php
new file mode 100644
index 0000000..0dd411d
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php
@@ -0,0 +1,123 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Acme;
+
+class Tester
+{
+ public function test($handler, $record)
+ {
+ $handler->handle($record);
+ }
+}
+
+function tester($handler, $record)
+{
+ $handler->handle($record);
+}
+
+namespace Monolog\Processor;
+
+use Monolog\Logger;
+use Monolog\TestCase;
+use Monolog\Handler\TestHandler;
+
+class IntrospectionProcessorTest extends TestCase
+{
+ public function getHandler()
+ {
+ $processor = new IntrospectionProcessor();
+ $handler = new TestHandler();
+ $handler->pushProcessor($processor);
+
+ return $handler;
+ }
+
+ public function testProcessorFromClass()
+ {
+ $handler = $this->getHandler();
+ $tester = new \Acme\Tester;
+ $tester->test($handler, $this->getRecord());
+ list($record) = $handler->getRecords();
+ $this->assertEquals(__FILE__, $record['extra']['file']);
+ $this->assertEquals(18, $record['extra']['line']);
+ $this->assertEquals('Acme\Tester', $record['extra']['class']);
+ $this->assertEquals('test', $record['extra']['function']);
+ }
+
+ public function testProcessorFromFunc()
+ {
+ $handler = $this->getHandler();
+ \Acme\tester($handler, $this->getRecord());
+ list($record) = $handler->getRecords();
+ $this->assertEquals(__FILE__, $record['extra']['file']);
+ $this->assertEquals(24, $record['extra']['line']);
+ $this->assertEquals(null, $record['extra']['class']);
+ $this->assertEquals('Acme\tester', $record['extra']['function']);
+ }
+
+ public function testLevelTooLow()
+ {
+ $input = array(
+ 'level' => Logger::DEBUG,
+ 'extra' => array(),
+ );
+
+ $expected = $input;
+
+ $processor = new IntrospectionProcessor(Logger::CRITICAL);
+ $actual = $processor($input);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testLevelEqual()
+ {
+ $input = array(
+ 'level' => Logger::CRITICAL,
+ 'extra' => array(),
+ );
+
+ $expected = $input;
+ $expected['extra'] = array(
+ 'file' => null,
+ 'line' => null,
+ 'class' => 'ReflectionMethod',
+ 'function' => 'invokeArgs',
+ );
+
+ $processor = new IntrospectionProcessor(Logger::CRITICAL);
+ $actual = $processor($input);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testLevelHigher()
+ {
+ $input = array(
+ 'level' => Logger::EMERGENCY,
+ 'extra' => array(),
+ );
+
+ $expected = $input;
+ $expected['extra'] = array(
+ 'file' => null,
+ 'line' => null,
+ 'class' => 'ReflectionMethod',
+ 'function' => 'invokeArgs',
+ );
+
+ $processor = new IntrospectionProcessor(Logger::CRITICAL);
+ $actual = $processor($input);
+
+ $this->assertEquals($expected, $actual);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php
new file mode 100644
index 0000000..eb66614
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\TestCase;
+
+class MemoryPeakUsageProcessorTest extends TestCase
+{
+ /**
+ * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke
+ * @covers Monolog\Processor\MemoryProcessor::formatBytes
+ */
+ public function testProcessor()
+ {
+ $processor = new MemoryPeakUsageProcessor();
+ $record = $processor($this->getRecord());
+ $this->assertArrayHasKey('memory_peak_usage', $record['extra']);
+ $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_peak_usage']);
+ }
+
+ /**
+ * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke
+ * @covers Monolog\Processor\MemoryProcessor::formatBytes
+ */
+ public function testProcessorWithoutFormatting()
+ {
+ $processor = new MemoryPeakUsageProcessor(true, false);
+ $record = $processor($this->getRecord());
+ $this->assertArrayHasKey('memory_peak_usage', $record['extra']);
+ $this->assertInternalType('int', $record['extra']['memory_peak_usage']);
+ $this->assertGreaterThan(0, $record['extra']['memory_peak_usage']);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php
new file mode 100644
index 0000000..4692dbf
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\TestCase;
+
+class MemoryUsageProcessorTest extends TestCase
+{
+ /**
+ * @covers Monolog\Processor\MemoryUsageProcessor::__invoke
+ * @covers Monolog\Processor\MemoryProcessor::formatBytes
+ */
+ public function testProcessor()
+ {
+ $processor = new MemoryUsageProcessor();
+ $record = $processor($this->getRecord());
+ $this->assertArrayHasKey('memory_usage', $record['extra']);
+ $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_usage']);
+ }
+
+ /**
+ * @covers Monolog\Processor\MemoryUsageProcessor::__invoke
+ * @covers Monolog\Processor\MemoryProcessor::formatBytes
+ */
+ public function testProcessorWithoutFormatting()
+ {
+ $processor = new MemoryUsageProcessor(true, false);
+ $record = $processor($this->getRecord());
+ $this->assertArrayHasKey('memory_usage', $record['extra']);
+ $this->assertInternalType('int', $record['extra']['memory_usage']);
+ $this->assertGreaterThan(0, $record['extra']['memory_usage']);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php
new file mode 100644
index 0000000..11f2b35
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\TestCase;
+
+class MercurialProcessorTest extends TestCase
+{
+ /**
+ * @covers Monolog\Processor\MercurialProcessor::__invoke
+ */
+ public function testProcessor()
+ {
+ if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+ exec("where hg 2>NUL", $output, $result);
+ } else {
+ exec("which hg 2>/dev/null >/dev/null", $output, $result);
+ }
+ if ($result != 0) {
+ $this->markTestSkipped('hg is missing');
+ return;
+ }
+
+ `hg init`;
+ $processor = new MercurialProcessor();
+ $record = $processor($this->getRecord());
+
+ $this->assertArrayHasKey('hg', $record['extra']);
+ $this->assertTrue(!is_array($record['extra']['hg']['branch']));
+ $this->assertTrue(!is_array($record['extra']['hg']['revision']));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php
new file mode 100644
index 0000000..458d2a3
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\TestCase;
+
+class ProcessIdProcessorTest extends TestCase
+{
+ /**
+ * @covers Monolog\Processor\ProcessIdProcessor::__invoke
+ */
+ public function testProcessor()
+ {
+ $processor = new ProcessIdProcessor();
+ $record = $processor($this->getRecord());
+ $this->assertArrayHasKey('process_id', $record['extra']);
+ $this->assertInternalType('int', $record['extra']['process_id']);
+ $this->assertGreaterThan(0, $record['extra']['process_id']);
+ $this->assertEquals(getmypid(), $record['extra']['process_id']);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php
new file mode 100644
index 0000000..029a0c0
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+class PsrLogMessageProcessorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getPairs
+ */
+ public function testReplacement($val, $expected)
+ {
+ $proc = new PsrLogMessageProcessor;
+
+ $message = $proc(array(
+ 'message' => '{foo}',
+ 'context' => array('foo' => $val),
+ ));
+ $this->assertEquals($expected, $message['message']);
+ }
+
+ public function getPairs()
+ {
+ return array(
+ array('foo', 'foo'),
+ array('3', '3'),
+ array(3, '3'),
+ array(null, ''),
+ array(true, '1'),
+ array(false, ''),
+ array(new \stdClass, '[object stdClass]'),
+ array(array(), '[array]'),
+ );
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php
new file mode 100644
index 0000000..0d860c6
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\TestCase;
+
+class TagProcessorTest extends TestCase
+{
+ /**
+ * @covers Monolog\Processor\TagProcessor::__invoke
+ */
+ public function testProcessor()
+ {
+ $tags = array(1, 2, 3);
+ $processor = new TagProcessor($tags);
+ $record = $processor($this->getRecord());
+
+ $this->assertEquals($tags, $record['extra']['tags']);
+ }
+
+ /**
+ * @covers Monolog\Processor\TagProcessor::__invoke
+ */
+ public function testProcessorTagModification()
+ {
+ $tags = array(1, 2, 3);
+ $processor = new TagProcessor($tags);
+
+ $record = $processor($this->getRecord());
+ $this->assertEquals($tags, $record['extra']['tags']);
+
+ $processor->setTags(array('a', 'b'));
+ $record = $processor($this->getRecord());
+ $this->assertEquals(array('a', 'b'), $record['extra']['tags']);
+
+ $processor->addTags(array('a', 'c', 'foo' => 'bar'));
+ $record = $processor($this->getRecord());
+ $this->assertEquals(array('a', 'b', 'a', 'c', 'foo' => 'bar'), $record['extra']['tags']);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php
new file mode 100644
index 0000000..5d13058
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\TestCase;
+
+class UidProcessorTest extends TestCase
+{
+ /**
+ * @covers Monolog\Processor\UidProcessor::__invoke
+ */
+ public function testProcessor()
+ {
+ $processor = new UidProcessor();
+ $record = $processor($this->getRecord());
+ $this->assertArrayHasKey('uid', $record['extra']);
+ }
+
+ public function testGetUid()
+ {
+ $processor = new UidProcessor(10);
+ $this->assertEquals(10, strlen($processor->getUid()));
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php b/core/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php
new file mode 100644
index 0000000..4105baf
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Processor;
+
+use Monolog\TestCase;
+
+class WebProcessorTest extends TestCase
+{
+ public function testProcessor()
+ {
+ $server = array(
+ 'REQUEST_URI' => 'A',
+ 'REMOTE_ADDR' => 'B',
+ 'REQUEST_METHOD' => 'C',
+ 'HTTP_REFERER' => 'D',
+ 'SERVER_NAME' => 'F',
+ 'UNIQUE_ID' => 'G',
+ );
+
+ $processor = new WebProcessor($server);
+ $record = $processor($this->getRecord());
+ $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']);
+ $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']);
+ $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']);
+ $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']);
+ $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']);
+ $this->assertEquals($server['UNIQUE_ID'], $record['extra']['unique_id']);
+ }
+
+ public function testProcessorDoNothingIfNoRequestUri()
+ {
+ $server = array(
+ 'REMOTE_ADDR' => 'B',
+ 'REQUEST_METHOD' => 'C',
+ );
+ $processor = new WebProcessor($server);
+ $record = $processor($this->getRecord());
+ $this->assertEmpty($record['extra']);
+ }
+
+ public function testProcessorReturnNullIfNoHttpReferer()
+ {
+ $server = array(
+ 'REQUEST_URI' => 'A',
+ 'REMOTE_ADDR' => 'B',
+ 'REQUEST_METHOD' => 'C',
+ 'SERVER_NAME' => 'F',
+ );
+ $processor = new WebProcessor($server);
+ $record = $processor($this->getRecord());
+ $this->assertNull($record['extra']['referrer']);
+ }
+
+ public function testProcessorDoesNotAddUniqueIdIfNotPresent()
+ {
+ $server = array(
+ 'REQUEST_URI' => 'A',
+ 'REMOTE_ADDR' => 'B',
+ 'REQUEST_METHOD' => 'C',
+ 'SERVER_NAME' => 'F',
+ );
+ $processor = new WebProcessor($server);
+ $record = $processor($this->getRecord());
+ $this->assertFalse(isset($record['extra']['unique_id']));
+ }
+
+ public function testProcessorAddsOnlyRequestedExtraFields()
+ {
+ $server = array(
+ 'REQUEST_URI' => 'A',
+ 'REMOTE_ADDR' => 'B',
+ 'REQUEST_METHOD' => 'C',
+ 'SERVER_NAME' => 'F',
+ );
+
+ $processor = new WebProcessor($server, array('url', 'http_method'));
+ $record = $processor($this->getRecord());
+
+ $this->assertSame(array('url' => 'A', 'http_method' => 'C'), $record['extra']);
+ }
+
+ public function testProcessorConfiguringOfExtraFields()
+ {
+ $server = array(
+ 'REQUEST_URI' => 'A',
+ 'REMOTE_ADDR' => 'B',
+ 'REQUEST_METHOD' => 'C',
+ 'SERVER_NAME' => 'F',
+ );
+
+ $processor = new WebProcessor($server, array('url' => 'REMOTE_ADDR'));
+ $record = $processor($this->getRecord());
+
+ $this->assertSame(array('url' => 'B'), $record['extra']);
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testInvalidData()
+ {
+ new WebProcessor(new \stdClass);
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php b/core/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php
new file mode 100644
index 0000000..ab89944
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+use Monolog\Handler\TestHandler;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Processor\PsrLogMessageProcessor;
+use Psr\Log\Test\LoggerInterfaceTest;
+
+class PsrLogCompatTest extends LoggerInterfaceTest
+{
+ private $handler;
+
+ public function getLogger()
+ {
+ $logger = new Logger('foo');
+ $logger->pushHandler($handler = new TestHandler);
+ $logger->pushProcessor(new PsrLogMessageProcessor);
+ $handler->setFormatter(new LineFormatter('%level_name% %message%'));
+
+ $this->handler = $handler;
+
+ return $logger;
+ }
+
+ public function getLogs()
+ {
+ $convert = function ($record) {
+ $lower = function ($match) {
+ return strtolower($match[0]);
+ };
+
+ return preg_replace_callback('{^[A-Z]+}', $lower, $record['formatted']);
+ };
+
+ return array_map($convert, $this->handler->getRecords());
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/RegistryTest.php b/core/vendor/monolog/monolog/tests/Monolog/RegistryTest.php
new file mode 100644
index 0000000..15fdfbd
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/RegistryTest.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+class RegistryTest extends \PHPUnit_Framework_TestCase
+{
+ protected function setUp()
+ {
+ Registry::clear();
+ }
+
+ /**
+ * @dataProvider hasLoggerProvider
+ * @covers Monolog\Registry::hasLogger
+ */
+ public function testHasLogger(array $loggersToAdd, array $loggersToCheck, array $expectedResult)
+ {
+ foreach ($loggersToAdd as $loggerToAdd) {
+ Registry::addLogger($loggerToAdd);
+ }
+ foreach ($loggersToCheck as $index => $loggerToCheck) {
+ $this->assertSame($expectedResult[$index], Registry::hasLogger($loggerToCheck));
+ }
+ }
+
+ public function hasLoggerProvider()
+ {
+ $logger1 = new Logger('test1');
+ $logger2 = new Logger('test2');
+ $logger3 = new Logger('test3');
+
+ return array(
+ // only instances
+ array(
+ array($logger1),
+ array($logger1, $logger2),
+ array(true, false),
+ ),
+ // only names
+ array(
+ array($logger1),
+ array('test1', 'test2'),
+ array(true, false),
+ ),
+ // mixed case
+ array(
+ array($logger1, $logger2),
+ array('test1', $logger2, 'test3', $logger3),
+ array(true, true, false, false),
+ ),
+ );
+ }
+
+ /**
+ * @covers Monolog\Registry::clear
+ */
+ public function testClearClears()
+ {
+ Registry::addLogger(new Logger('test1'), 'log');
+ Registry::clear();
+
+ $this->setExpectedException('\InvalidArgumentException');
+ Registry::getInstance('log');
+ }
+
+ /**
+ * @dataProvider removedLoggerProvider
+ * @covers Monolog\Registry::addLogger
+ * @covers Monolog\Registry::removeLogger
+ */
+ public function testRemovesLogger($loggerToAdd, $remove)
+ {
+ Registry::addLogger($loggerToAdd);
+ Registry::removeLogger($remove);
+
+ $this->setExpectedException('\InvalidArgumentException');
+ Registry::getInstance($loggerToAdd->getName());
+ }
+
+ public function removedLoggerProvider()
+ {
+ $logger1 = new Logger('test1');
+
+ return array(
+ array($logger1, $logger1),
+ array($logger1, 'test1'),
+ );
+ }
+
+ /**
+ * @covers Monolog\Registry::addLogger
+ * @covers Monolog\Registry::getInstance
+ * @covers Monolog\Registry::__callStatic
+ */
+ public function testGetsSameLogger()
+ {
+ $logger1 = new Logger('test1');
+ $logger2 = new Logger('test2');
+
+ Registry::addLogger($logger1, 'test1');
+ Registry::addLogger($logger2);
+
+ $this->assertSame($logger1, Registry::getInstance('test1'));
+ $this->assertSame($logger2, Registry::test2());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @covers Monolog\Registry::getInstance
+ */
+ public function testFailsOnNonExistantLogger()
+ {
+ Registry::getInstance('test1');
+ }
+
+ /**
+ * @covers Monolog\Registry::addLogger
+ */
+ public function testReplacesLogger()
+ {
+ $log1 = new Logger('test1');
+ $log2 = new Logger('test2');
+
+ Registry::addLogger($log1, 'log');
+
+ Registry::addLogger($log2, 'log', true);
+
+ $this->assertSame($log2, Registry::getInstance('log'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @covers Monolog\Registry::addLogger
+ */
+ public function testFailsOnUnspecifiedReplacement()
+ {
+ $log1 = new Logger('test1');
+ $log2 = new Logger('test2');
+
+ Registry::addLogger($log1, 'log');
+
+ Registry::addLogger($log2, 'log');
+ }
+}
diff --git a/core/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php b/core/vendor/monolog/monolog/tests/Monolog/SignalHandlerTest.php
new file mode 100644
index 0000000..9fa0792
--- /dev/null
+++ b/core/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/core/vendor/monolog/monolog/tests/Monolog/TestCase.php b/core/vendor/monolog/monolog/tests/Monolog/TestCase.php
new file mode 100644
index 0000000..4eb7b4c
--- /dev/null
+++ b/core/vendor/monolog/monolog/tests/Monolog/TestCase.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+class TestCase extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @return array Record
+ */
+ protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array())
+ {
+ return array(
+ 'message' => $message,
+ 'context' => $context,
+ 'level' => $level,
+ 'level_name' => Logger::getLevelName($level),
+ 'channel' => 'test',
+ 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))),
+ 'extra' => array(),
+ );
+ }
+
+ /**
+ * @return array
+ */
+ protected function getMultipleRecords()
+ {
+ return array(
+ $this->getRecord(Logger::DEBUG, 'debug message 1'),
+ $this->getRecord(Logger::DEBUG, 'debug message 2'),
+ $this->getRecord(Logger::INFO, 'information'),
+ $this->getRecord(Logger::WARNING, 'warning'),
+ $this->getRecord(Logger::ERROR, 'error'),
+ );
+ }
+
+ /**
+ * @return Monolog\Formatter\FormatterInterface
+ */
+ protected function getIdentityFormatter()
+ {
+ $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $formatter->expects($this->any())
+ ->method('format')
+ ->will($this->returnCallback(function ($record) { return $record['message']; }));
+
+ return $formatter;
+ }
+}
diff --git a/core/vendor/morilog/jalali/.gitignore b/core/vendor/morilog/jalali/.gitignore
new file mode 100644
index 0000000..38cdbd7
--- /dev/null
+++ b/core/vendor/morilog/jalali/.gitignore
@@ -0,0 +1,5 @@
+/vendor
+composer.phar
+composer.lock
+.DS_Store
+.idea
\ No newline at end of file
diff --git a/core/vendor/morilog/jalali/.travis.yml b/core/vendor/morilog/jalali/.travis.yml
new file mode 100644
index 0000000..f817604
--- /dev/null
+++ b/core/vendor/morilog/jalali/.travis.yml
@@ -0,0 +1,11 @@
+language: php
+
+php:
+ - 5.5
+ - 5.6
+
+before_script:
+ - curl -s http://getcomposer.org/installer | php
+ - php composer.phar install --dev
+
+script: phpunit
diff --git a/core/vendor/morilog/jalali/LICENSE b/core/vendor/morilog/jalali/LICENSE
new file mode 100644
index 0000000..8d2fb98
--- /dev/null
+++ b/core/vendor/morilog/jalali/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Morilog
+
+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/core/vendor/morilog/jalali/README.md b/core/vendor/morilog/jalali/README.md
new file mode 100644
index 0000000..10027b1
--- /dev/null
+++ b/core/vendor/morilog/jalali/README.md
@@ -0,0 +1,146 @@
+[![Build Status](https://travis-ci.org/morilog/jalali.svg?branch=master)](https://travis-ci.org/morilog/jalali)
+morilog/jalali
+======
+- This package compatible with Laravel `>=5` & `< 6.0`
+
+- This package was forked from [Miladr/Jalali](http://github.com/miladr/jalai) in previous version and fixed bugs a
+
+- Jalali calendar is a solar calendar that was used in Persia, variants of which today are still in use in Iran as well as Afghanistan. [Read more on Wikipedia](http://en.wikipedia.org/wiki/Jalali_calendar) or see [Calendar Converter](http://www.fourmilab.ch/documents/calendar/).
+
+- Calendar conversion is based on the [algorithm provided by Kazimierz M. Borkowski](http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm) and has a very good performance.
+
+- jDateTime class was ported from [jalaali/jalaali-js](https://github.com/jalaali/jalaali-js)
+
+## Installation Version 2.0
+> If you are using version < 2.0, please read [old docs](https://github.com/morilog/jalali/blob/v1.1/README.md)
+
+Run the Composer update comand
+
+ $ composer require morilog/jalali
+
+In your `config/app.php` add `'Morilog\Jalali\JalaliServiceProvider'` to the end of the `$providers` array
+
+```php
+'providers' => [
+
+ Illuminate\Foundation\Providers\ArtisanServiceProvider::class,
+ Illuminate\Auth\AuthServiceProvider::class,
+ ...
+ Morilog\Jalali\JalaliServiceProvider::class,
+
+],
+
+
+'alias' => [
+ ...
+ 'jDate' => Morilog\Jalali\Facades\jDate::class
+]
+```
+
+
+## Basic Usage
+
+### jDate
+In version >= 1.1, You can use `jdate()` instead of `jDate::forge()`;
+#### `forge([$str = '', $timestamp = null])`
+``` php
+// default timestamp is now
+$date = \Morilog\Jalali\jDate::forge();
+// OR
+$date = jdate();
+
+// pass timestamps
+$date = jDate::forge(1333857600);
+// OR
+$date = jdate(1333857600);
+
+// pass strings to make timestamps
+$date = jDate::forge('last sunday');
+
+// get the timestamp
+$date = jDate::forge('last sunday')->time(); // 1333857600
+
+// format the timestamp
+$date = jDate::forge('last sunday')->format('%B %d، %Y'); // دی 02، 1391
+
+// get a predefined format
+$date = jDate::forge('last sunday')->format('datetime'); // 1391-10-02 00:00:00
+$date = jDate::forge('last sunday')->format('date'); // 1391-10-02
+$date = jDate::forge('last sunday')->format('time'); // 00:00:00
+
+// amend the timestamp value, relative to existing value
+$date = jDate::forge('2012-10-12')->reforge('+ 3 days')->format('date'); // 1391-07-24
+
+// get relative 'ago' format
+$date = jDate::forge('now - 10 minutes')->ago() // 10 دقیقه پیش
+// OR
+$date = jdate('now - 10 minutes')->ago() // 10 دقیقه پیش
+```
+
+### jDateTime
+---
+
+
+#### `checkDate($year, $month, $day, [$isJalali = true])`
+```php
+// Check jalali date
+\Morilog\Jalali\jDateTime::checkDate(1391, 2, 30, true); // true
+
+// Check jalali date
+\Morilog\Jalali\jDateTime::checkDate(2016, 5, 7); // false
+
+// Check gregorian date
+\Morilog\Jalali\jDateTime::checkDate(2016, 5, 7, false); // true
+```
+---
+#### `toJalali($gYear, $gMonth, $gDay)`
+```php
+\Morilog\Jalali\jDateTime::toJalali(2016, 5, 7); // [1395, 2, 18]
+```
+---
+#### `toGregorian($jYear, $jMonth, $jDay)`
+```php
+\Morilog\Jalali\jDateTime::toGregorian(1395, 2, 18); // [2016, 5, 7]
+```
+---
+#### `strftime($format, [$timestamp = false, $timezone = null])`
+```php
+jDateTime::strftime('Y-m-d', strtotime('2016-05-8')); // 1395-02-19
+```
+---
+#### `createDateTimeFromFormat($format, $jalaiTimeString)`
+```php
+$jdate = '1394/11/25 15:00:00';
+
+// get instance of \DateTime
+$dateTime = \Morilog\Jalali\jDatetime::createDatetimeFromFormat('Y/m/d H:i:s', $jdate);
+
+```
+---
+#### `createCarbonFromFormat($format, $jalaiTimeString)`
+```php
+$jdate = '1394/11/25 15:00:00';
+
+// get instance of \Carbon\Carbon
+$carbon = \Morilog\Jalali\jDatetime::createDatetimeFromFormat('Y/m/d H:i:s', $jdate);
+
+```
+---
+#### `convertNumbers($string)`
+```php
+$date = \Morilog\Jalali\jDateTime::strftime('Y-m-d', strtotime('2016-05-8'); // 1395-02-19
+\Morilog\Jalali\jDateTime::convertNumbers($date); // ۱۳۹۵-۰۲-۱۹
+```
+---
+## Formatting ##
+
+For help in building your formats, checkout the [PHP strftime() docs](http://php.net/manual/en/function.strftime.php).
+
+## Notes ##
+
+The class relies on ``strtotime()`` to make sense of your strings, and ``strftime()`` to make the format changes. Just always check the ``time()`` output to see if you get false timestamps... which means the class couldn't understand what you were telling it.
+
+## License ##
+- This bundle is created based on [Laravel-Date](https://github.com/swt83/laravel-date) by [Scott Travis](https://github.com/swt83) (MIT Licensed).
+- [Jalali (Shamsi) DateTime](https://github.com/sallar/jDateTime) class included in the package is created by [Sallar Kaboli](http://sallar.me) and is released under the MIT License.
+- This package was created and modified by [Morteza Parvini](http://morilog.ir) for Laravel >= 5 and is released under the MIT License.
diff --git a/core/vendor/morilog/jalali/composer.json b/core/vendor/morilog/jalali/composer.json
new file mode 100644
index 0000000..20a45bf
--- /dev/null
+++ b/core/vendor/morilog/jalali/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "morilog/jalali",
+ "description": "eThis Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in Laravel 5 applications, based on Jalali (Shamsi) DateTime class.",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Milad Rey",
+ "email": "miladr@gmail.com"
+ },
+ {
+ "name": "Morteza Parvini",
+ "email": "m.parvini@outlook.com"
+ }
+ ],
+ "keywords": ["Laravel","Date","Datetime","Jalali", "Morilog"],
+ "require": {
+ "php": ">=5.5",
+ "illuminate/support": "^5.0",
+ "nesbot/carbon": "^1.21"
+ },
+ "require-dev" : {
+ "phpunit/phpunit": "~4.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Morilog\\Jalali\\": "src"
+ },
+ "files": [
+ "src/helpers.php"
+ ]
+ },
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Morilog\\Jalali\\JalaliServiceProvider"
+ ],
+ "aliases": {
+ "jDate": "Morilog\\Jalali\\Facades\\jDate"
+ }
+ }
+ },
+ "minimum-stability": "dev"
+}
diff --git a/core/vendor/morilog/jalali/phpunit.xml b/core/vendor/morilog/jalali/phpunit.xml
new file mode 100644
index 0000000..4eceac6
--- /dev/null
+++ b/core/vendor/morilog/jalali/phpunit.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ ./tests/
+
+
+
\ No newline at end of file
diff --git a/core/vendor/morilog/jalali/src/Facades/jDate.php b/core/vendor/morilog/jalali/src/Facades/jDate.php
new file mode 100644
index 0000000..df6df7b
--- /dev/null
+++ b/core/vendor/morilog/jalali/src/Facades/jDate.php
@@ -0,0 +1,19 @@
+app->bind('jalali', function ($app) {
+ return new jDate;
+ });
+
+ $this->app->bind('jDateTime', function ($app) {
+ return new jDateTime;
+ });
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides()
+ {
+ return array('jalali', 'jDateTime');
+ }
+
+}
diff --git a/core/vendor/morilog/jalali/src/helpers.php b/core/vendor/morilog/jalali/src/helpers.php
new file mode 100644
index 0000000..3bb250d
--- /dev/null
+++ b/core/vendor/morilog/jalali/src/helpers.php
@@ -0,0 +1,13 @@
+
+ *
+ *
+ * Based on Laravel-Date bundle
+ * by Scott Travis
+ * http://github.com/swt83/laravel-date
+ *
+ *
+ * @package jDate
+ * @author Sallar Kaboli
+ * @author Morteza Parvini
+ * @link http://
+ * @basedon http://github.com/swt83/laravel-date
+ * @license MIT License
+ */
+use Carbon\Carbon;
+
+/**
+ * Class jDate
+ * @package Morilog\Jalali
+ */
+class jDate
+{
+ /**
+ * @var \DateTime
+ */
+ protected $dateTime;
+
+
+ /**
+ * @var array
+ */
+ protected $formats = array(
+ 'datetime' => '%Y-%m-%d %H:%M:%S',
+ 'date' => '%Y-%m-%d',
+ 'time' => '%H:%M:%S',
+ );
+
+ /**
+ * @param string|null $str
+ * @param null $timezone
+ * @return $this
+ */
+ public static function forge($str = null, $timezone = null)
+ {
+ return new static($str, $timezone);
+ }
+
+ /**
+ * @param string|null $str
+ * @param null $timezone
+ */
+ public function __construct($str = null, $timezone = null)
+ {
+ $this->dateTime = jDateTime::createDateTime($str, $timezone);
+ }
+
+ /**
+ * @return \DateTime|static
+ */
+ public function getDateTime()
+ {
+ return $this->dateTime;
+ }
+
+ /**
+ * @param $format
+ * @return bool|string
+ */
+ public function format($format)
+ {
+ // convert alias string
+ if (in_array($format, array_keys($this->formats))) {
+ $format = $this->formats[$format];
+ }
+
+ // if valid unix timestamp...
+ if ($this->dateTime !== false) {
+ return jDateTime::strftime($format, $this->dateTime->getTimestamp(), $this->dateTime->getTimezone());
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @param string $str
+ * @return $this
+ */
+ public function reforge($str)
+ {
+ $this->dateTime->modify($str);
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function ago()
+ {
+ $now = time();
+ $time = $this->getDateTime()->getTimestamp();
+
+ // catch error
+ if (!$time) {
+ return false;
+ }
+
+ // build period and length arrays
+ $periods = array('ثانیه', 'دقیقه', 'ساعت', 'روز', 'هفته', 'ماه', 'سال', 'قرن');
+ $lengths = array(60, 60, 24, 7, 4.35, 12, 10);
+
+ // get difference
+ $difference = $now - $time;
+
+ // set descriptor
+ if ($difference < 0) {
+ $difference = abs($difference); // absolute value
+ $negative = true;
+ }
+
+ // do math
+ for ($j = 0; $difference >= $lengths[$j] and $j < count($lengths) - 1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ // round difference
+ $difference = intval(round($difference));
+
+ // return
+ return number_format($difference) . ' ' . $periods[$j] . ' ' . (isset($negative) ? '' : 'پیش');
+ }
+
+ /**
+ * @return bool|string
+ */
+ public function until()
+ {
+ return $this->ago();
+ }
+
+ /**
+ * @return int
+ */
+ public function time()
+ {
+ return $this->dateTime->getTimestamp();
+ }
+
+}
diff --git a/core/vendor/morilog/jalali/src/jDateTime.php b/core/vendor/morilog/jalali/src/jDateTime.php
new file mode 100644
index 0000000..50a8ecc
--- /dev/null
+++ b/core/vendor/morilog/jalali/src/jDateTime.php
@@ -0,0 +1,868 @@
+setDate($year, $month, $day);
+
+
+ return $georgianDate;
+ }
+
+ /**
+ * Checks whether a Jalaali date is valid or not.
+ *
+ * @param int $jy
+ * @param int $jm
+ * @param int $jd
+ * @return bool
+ */
+ public static function isValidateJalaliDate($jy, $jm, $jd)
+ {
+ return $jy >= -61 && $jy <= 3177
+ && $jm >= 1 && $jm <= 12
+ && $jd >= 1 && $jd <= self::jalaliMonthLength($jy, $jm);
+ }
+
+ /**
+ * Checks whether a date is valid or not.
+ *
+ * @param $year
+ * @param $month
+ * @param $day
+ * @param bool $isJalali
+ * @return bool
+ */
+ public static function checkDate($year, $month, $day, $isJalali = true)
+ {
+ return $isJalali === true ? self::isValidateJalaliDate($year, $month, $day) : checkdate($month, $day, $year);
+ }
+
+ /**
+ * Is this a leap year or not?
+ *
+ * @param $jy
+ * @return bool
+ */
+ public static function isLeapJalaliYear($jy)
+ {
+ return self::jalaliCal($jy)['leap'] === 0;
+ }
+
+ /**
+ * Number of days in a given month in a Jalaali year.
+ *
+ * @param int $jy
+ * @param int $jm
+ * @return int
+ */
+ public static function jalaliMonthLength($jy, $jm)
+ {
+ if ($jm <= 6) {
+ return 31;
+ }
+
+ if ($jm <= 11) {
+ return 30;
+ }
+
+ return self::isLeapJalaliYear($jy) ? 30 : 29;
+ }
+
+
+ /**
+ * This function determines if the Jalaali (Persian) year is
+ * leap (366-day long) or is the common year (365 days), and
+ * finds the day in March (Gregorian calendar) of the first
+ * day of the Jalaali year (jy).
+ *
+ * @param int $jy Jalaali calendar year (-61 to 3177)
+ * @return array
+ * leap: number of years since the last leap year (0 to 4)
+ * gy: Gregorian year of the beginning of Jalaali year
+ * march: the March day of Farvardin the 1st (1st day of jy)
+ * @see: http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm
+ * @see: http://www.fourmilab.ch/documents/calendar/
+ */
+ public static function jalaliCal($jy)
+ {
+ $breaks = [-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210
+ , 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
+ ];
+
+ $breaksCount = count($breaks);
+
+ $gy = $jy + 621;
+ $leapJ = -14;
+ $jp = $breaks[0];
+
+ if ($jy < $jp || $jy >= $breaks[$breaksCount - 1]) {
+ throw new \InvalidArgumentException('Invalid Jalali year : ' . $jy);
+ }
+
+ $jump = 0;
+
+ for ($i = 1; $i < $breaksCount; $i += 1) {
+ $jm = $breaks[$i];
+ $jump = $jm - $jp;
+
+ if ($jy < $jm) {
+ break;
+ }
+
+ $leapJ = $leapJ + self::div($jump, 33) * 8 + self::div(self::mod($jump, 33), 4);
+
+ $jp = $jm;
+ }
+
+ $n = $jy - $jp;
+
+ $leapJ = $leapJ + self::div($n, 33) * 8 + self::div(self::mod($n, 33) + 3, 4);
+
+ if (self::mod($jump, 33) === 4 && $jump - $n === 4) {
+ $leapJ += 1;
+ }
+
+ $leapG = self::div($gy, 4) - self::div((self::div($gy, 100) + 1) * 3, 4) - 150;
+
+ $march = 20 + $leapJ - $leapG;
+
+ if ($jump - $n < 6) {
+ $n = $n - $jump + self::div($jump + 4, 33) * 33;
+ }
+
+ $leap = self::mod(self::mod($n + 1, 33) - 1, 4);
+
+ if ($leap === -1) {
+ $leap = 4;
+ }
+
+ return [
+ 'leap' => $leap,
+ 'gy' => $gy,
+ 'march' => $march
+ ];
+ }
+
+ /**
+ * @param $a
+ * @param $b
+ * @return float
+ */
+ public static function div($a, $b)
+ {
+ return ~~($a / $b);
+ }
+
+ /**
+ * @param $a
+ * @param $b
+ * @return mixed
+ */
+ public static function mod($a, $b)
+ {
+ return $a - ~~($a / $b) * $b;
+ }
+
+ /**
+ * @param $jdn
+ * @return array
+ */
+ public static function d2g($jdn)
+ {
+ $j = 4 * $jdn + 139361631;
+ $j += self::div(self::div(4 * $jdn + 183187720, 146097) * 3, 4) * 4 - 3908;
+ $i = self::div(self::mod($j, 1461), 4) * 5 + 308;
+
+ $gd = self::div(self::mod($i, 153), 5) + 1;
+ $gm = self::mod(self::div($i, 153), 12) + 1;
+ $gy = self::div($j, 1461) - 100100 + self::div(8 - $gm, 6);
+
+ return [$gy, $gm, $gd];
+ }
+
+ /**
+ * Calculates the Julian Day number from Gregorian or Julian
+ * calendar dates. This integer number corresponds to the noon of
+ * the date (i.e. 12 hours of Universal Time).
+ * The procedure was tested to be good since 1 March, -100100 (of both
+ * calendars) up to a few million years into the future.
+ *
+ * @param int $gy Calendar year (years BC numbered 0, -1, -2, ...)
+ * @param int $gm Calendar month (1 to 12)
+ * @param int $gd Calendar day of the month (1 to 28/29/30/31)
+ * @return int Julian Day number
+ */
+ public static function g2d($gy, $gm, $gd)
+ {
+ return (
+ self::div(($gy + self::div($gm - 8, 6) + 100100) * 1461, 4)
+ + self::div(153 * self::mod($gm + 9, 12) + 2, 5)
+ + $gd - 34840408
+ ) - self::div(self::div($gy + 100100 + self::div($gm - 8, 6), 100) * 3, 4) + 752;
+
+ }
+
+ /**
+ * Converts a date of the Jalaali calendar to the Julian Day number.
+ *
+ * @param int $jy Jalaali year (1 to 3100)
+ * @param int $jm Jalaali month (1 to 12)
+ * @param int $jd Jalaali day (1 to 29/31)
+ * @return int Julian Day number
+ */
+ public static function j2d($jy, $jm, $jd)
+ {
+ $jCal = self::jalaliCal($jy);
+
+ return self::g2d($jCal['gy'], 3, $jCal['march']) + ($jm - 1) * 31 - self::div($jm, 7) * ($jm - 7) + $jd - 1;
+ }
+
+
+ /**
+ * Converts the Julian Day number to a date in the Jalaali calendar.
+ *
+ * @param int $jdn Julian Day number
+ * @return array
+ * 0: Jalaali year (1 to 3100)
+ * 1: Jalaali month (1 to 12)
+ * 2: Jalaali day (1 to 29/31)
+ */
+ public static function d2j($jdn)
+ {
+ $gy = self::d2g($jdn)[0];
+ $jy = $gy - 621;
+ $jCal = self::jalaliCal($jy);
+ $jdn1f = self::g2d($gy, 3, $jCal['march']);
+
+ $k = $jdn - $jdn1f;
+
+ if ($k >= 0) {
+ if ($k <= 185) {
+ $jm = 1 + self::div($k, 31);
+ $jd = self::mod($k, 31) + 1;
+
+ return [$jy, $jm, $jd];
+ } else {
+ $k -= 186;
+ }
+ } else {
+ $jy -= 1;
+ $k += 179;
+
+ if ($jCal['leap'] === 1) {
+ $k += 1;
+ }
+ }
+
+ $jm = 7 + self::div($k, 30);
+ $jd = self::mod($k, 30) + 1;
+
+ return [$jy, $jm, $jd];
+ }
+
+ /**
+ * @param $format
+ * @param bool $stamp
+ * @param bool $timezone
+ * @return mixed
+ */
+ public static function date($format, $stamp = false, $timezone = null)
+ {
+ $stamp = ($stamp !== false) ? $stamp : time();
+ $dateTime = static::createDateTime($stamp, $timezone);
+
+
+ //Find what to replace
+ $chars = (preg_match_all('/([a-zA-Z]{1})/', $format, $chars)) ? $chars[0] : array();
+
+ //Intact Keys
+ $intact = array('B', 'h', 'H', 'g', 'G', 'i', 's', 'I', 'U', 'u', 'Z', 'O', 'P');
+ $intact = self::filterArray($chars, $intact);
+ $intactValues = array();
+
+ foreach ($intact as $k => $v) {
+ $intactValues[$k] = $dateTime->format($v);
+ }
+ //End Intact Keys
+
+ //Changed Keys
+ list($year, $month, $day) = array($dateTime->format('Y'), $dateTime->format('n'), $dateTime->format('j'));
+ list($jYear, $jMonth, $jDay) = self::toJalali($year, $month, $day);
+
+ $keys = array(
+ 'd',
+ 'D',
+ 'j',
+ 'l',
+ 'N',
+ 'S',
+ 'w',
+ 'z',
+ 'W',
+ 'F',
+ 'm',
+ 'M',
+ 'n',
+ 't',
+ 'L',
+ 'o',
+ 'Y',
+ 'y',
+ 'a',
+ 'A',
+ 'c',
+ 'r',
+ 'e',
+ 'T'
+ );
+ $keys = self::filterArray($chars, $keys, array('z'));
+ $values = array();
+
+ foreach ($keys as $k => $key) {
+
+ $v = '';
+ switch ($key) {
+ //Day
+ case 'd':
+ $v = sprintf("%02d", $jDay);
+ break;
+ case 'D':
+ $v = self::getDayNames($dateTime->format('D'), true);
+ break;
+ case 'j':
+ $v = $jDay;
+ break;
+ case 'l':
+ $v = self::getDayNames($dateTime->format('l'));
+ break;
+ case 'N':
+ $v = self::getDayNames($dateTime->format('l'), false, 1, true);
+ break;
+ case 'S':
+ $v = 'ام';
+ break;
+ case 'w':
+ $v = self::getDayNames($dateTime->format('l'), false, 1, true) - 1;
+ break;
+ case 'z':
+ if ($jMonth > 6) {
+ $v = 186 + (($jMonth - 6 - 1) * 30) + $jDay;
+ } else {
+ $v = (($jMonth - 1) * 31) + $jDay;
+ }
+ self::$temp['z'] = $v;
+ break;
+ //Week
+ case 'W':
+ $v = is_int(self::$temp['z'] / 7) ? (self::$temp['z'] / 7) : intval(self::$temp['z'] / 7 + 1);
+ break;
+ //Month
+ case 'F':
+ $v = self::getMonthNames($jMonth);
+ break;
+ case 'm':
+ $v = sprintf("%02d", $jMonth);
+ break;
+ case 'M':
+ $v = self::getMonthNames($jMonth, true);
+ break;
+ case 'n':
+ $v = $jMonth;
+ break;
+ case 't':
+ $v = ($jMonth == 12) ? 29 : (($jMonth > 6 && $jMonth != 12) ? 30 : 31);
+ break;
+ //Year
+ case 'L':
+ $tmpObj = static::createDateTime(time() - 31536000, $timezone);
+ $v = $tmpObj->format('L');
+ break;
+ case 'o':
+ case 'Y':
+ $v = $jYear;
+ break;
+ case 'y':
+ $v = $jYear % 100;
+ break;
+ //Time
+ case 'a':
+ $v = ($dateTime->format('a') == 'am') ? 'ق.ظ' : 'ب.ظ';
+ break;
+ case 'A':
+ $v = ($dateTime->format('A') == 'AM') ? 'قبل از ظهر' : 'بعد از ظهر';
+ break;
+ //Full Dates
+ case 'c':
+ $v = $jYear . '-' . sprintf("%02d", $jMonth) . '-' . sprintf("%02d", $jDay) . 'T';
+ $v .= $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . $dateTime->format('P');
+ break;
+ case 'r':
+ $v = self::getDayNames($dateTime->format('D'), true) . ', ' . sprintf("%02d",
+ $jDay) . ' ' . self::getMonthNames($jMonth, true);
+ $v .= ' ' . $jYear . ' ' . $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . ' ' . $dateTime->format('P');
+ break;
+ //Timezone
+ case 'e':
+ $v = $dateTime->format('e');
+ break;
+ case 'T':
+ $v = $dateTime->format('T');
+ break;
+
+ }
+ $values[$k] = $v;
+
+ }
+ //End Changed Keys
+
+ //Merge
+ $keys = array_merge($intact, $keys);
+ $values = array_merge($intactValues, $values);
+
+ return strtr($format, array_combine($keys, $values));
+ }
+
+ /**
+ * @param $format
+ * @param bool $stamp
+ * @param null $timezone
+ * @return mixed
+ */
+ public static function strftime($format, $stamp = false, $timezone = null)
+ {
+ $str_format_code = array(
+ "%a",
+ "%A",
+ "%d",
+ "%e",
+ "%j",
+ "%u",
+ "%w",
+ "%U",
+ "%V",
+ "%W",
+ "%b",
+ "%B",
+ "%h",
+ "%m",
+ "%C",
+ "%g",
+ "%G",
+ "%y",
+ "%Y",
+ "%H",
+ "%I",
+ "%l",
+ "%M",
+ "%p",
+ "%P",
+ "%r",
+ "%R",
+ "%S",
+ "%T",
+ "%X",
+ "%z",
+ "%Z",
+ "%c",
+ "%D",
+ "%F",
+ "%s",
+ "%x",
+ "%n",
+ "%t",
+ "%%",
+ );
+
+ $date_format_code = array(
+ "D",
+ "l",
+ "d",
+ "j",
+ "z",
+ "N",
+ "w",
+ "W",
+ "W",
+ "W",
+ "M",
+ "F",
+ "M",
+ "m",
+ "y",
+ "y",
+ "y",
+ "y",
+ "Y",
+ "H",
+ "h",
+ "g",
+ "i",
+ "A",
+ "a",
+ "h:i:s A",
+ "H:i",
+ "s",
+ "H:i:s",
+ "h:i:s",
+ "H",
+ "H",
+ "D j M H:i:s",
+ "d/m/y",
+ "Y-m-d",
+ "U",
+ "d/m/y",
+ "\n",
+ "\t",
+ "%",
+ );
+
+ //Change Strftime format to Date format
+ $format = str_replace($str_format_code, $date_format_code, $format);
+
+ //Convert to date
+ return self::date($format, $stamp, $timezone);
+ }
+
+ private static function getDayNames($day, $shorten = false, $len = 1, $numeric = false)
+ {
+ switch (strtolower($day)) {
+ case 'sat':
+ case 'saturday':
+ $ret = 'شنبه';
+ $n = 1;
+ break;
+ case 'sun':
+ case 'sunday':
+ $ret = 'یکشنبه';
+ $n = 2;
+ break;
+ case 'mon':
+ case 'monday':
+ $ret = 'دوشنبه';
+ $n = 3;
+ break;
+ case 'tue':
+ case 'tuesday':
+ $ret = 'سه شنبه';
+ $n = 4;
+ break;
+ case 'wed':
+ case 'wednesday':
+ $ret = 'چهارشنبه';
+ $n = 5;
+ break;
+ case 'thu':
+ case 'thursday':
+ $ret = 'پنجشنبه';
+ $n = 6;
+ break;
+ case 'fri':
+ case 'friday':
+ $ret = 'جمعه';
+ $n = 7;
+ break;
+ default:
+ $ret = '';
+ $n = -1;
+ }
+
+ return ($numeric) ? $n : (($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret);
+ }
+
+ private static function getMonthNames($month, $shorten = false, $len = 3)
+ {
+ $ret = '';
+ switch ($month) {
+ case '1':
+ $ret = 'فروردین';
+ break;
+ case '2':
+ $ret = 'اردیبهشت';
+ break;
+ case '3':
+ $ret = 'خرداد';
+ break;
+ case '4':
+ $ret = 'تیر';
+ break;
+ case '5':
+ $ret = 'مرداد';
+ break;
+ case '6':
+ $ret = 'شهریور';
+ break;
+ case '7':
+ $ret = 'مهر';
+ break;
+ case '8':
+ $ret = 'آبان';
+ break;
+ case '9':
+ $ret = 'آذر';
+ break;
+ case '10':
+ $ret = 'دی';
+ break;
+ case '11':
+ $ret = 'بهمن';
+ break;
+ case '12':
+ $ret = 'اسفند';
+ break;
+ }
+
+ return ($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret;
+ }
+
+ private static function filterArray($needle, $haystack, $always = array())
+ {
+ foreach ($haystack as $k => $v) {
+ if (!in_array($v, $needle) && !in_array($v, $always)) {
+ unset($haystack[$k]);
+ }
+
+ }
+
+
+ return $haystack;
+ }
+
+
+ /**
+ * @param $format
+ * @param $date
+ * @return array
+ */
+ public static function parseFromFormat($format, $date)
+ {
+ // reverse engineer date formats
+ $keys = array(
+ 'Y' => array('year', '\d{4}'),
+ 'y' => array('year', '\d{2}'),
+ 'm' => array('month', '\d{2}'),
+ 'n' => array('month', '\d{1,2}'),
+ 'M' => array('month', '[A-Z][a-z]{3}'),
+ 'F' => array('month', '[A-Z][a-z]{2,8}'),
+ 'd' => array('day', '\d{2}'),
+ 'j' => array('day', '\d{1,2}'),
+ 'D' => array('day', '[A-Z][a-z]{2}'),
+ 'l' => array('day', '[A-Z][a-z]{6,9}'),
+ 'u' => array('hour', '\d{1,6}'),
+ 'h' => array('hour', '\d{2}'),
+ 'H' => array('hour', '\d{2}'),
+ 'g' => array('hour', '\d{1,2}'),
+ 'G' => array('hour', '\d{1,2}'),
+ 'i' => array('minute', '\d{2}'),
+ 's' => array('second', '\d{2}'),
+ );
+
+ // convert format string to regex
+ $regex = '';
+ $chars = str_split($format);
+ foreach ($chars as $n => $char) {
+ $lastChar = isset($chars[$n - 1]) ? $chars[$n - 1] : '';
+ $skipCurrent = '\\' == $lastChar;
+ if (!$skipCurrent && isset($keys[$char])) {
+ $regex .= '(?P<' . $keys[$char][0] . '>' . $keys[$char][1] . ')';
+ } else {
+ if ('\\' == $char) {
+ $regex .= $char;
+ } else {
+ $regex .= preg_quote($char);
+ }
+ }
+ }
+
+ $dt = array();
+ $dt['error_count'] = 0;
+ // now try to match it
+ if (preg_match('#^' . $regex . '$#', $date, $dt)) {
+ foreach ($dt as $k => $v) {
+ if (is_int($k)) {
+ unset($dt[$k]);
+ }
+ }
+ if (!jDateTime::checkdate($dt['month'], $dt['day'], $dt['year'], false)) {
+ $dt['error_count'] = 1;
+ }
+ } else {
+ $dt['error_count'] = 1;
+ }
+ $dt['errors'] = array();
+ $dt['fraction'] = '';
+ $dt['warning_count'] = 0;
+ $dt['warnings'] = array();
+ $dt['is_localtime'] = 0;
+ $dt['zone_type'] = 0;
+ $dt['zone'] = 0;
+ $dt['is_dst'] = '';
+
+ if (strlen($dt['year']) == 2) {
+ $now = jDate::forge('now');
+ $x = $now->format('Y') - $now->format('y');
+ $dt['year'] += $x;
+ }
+
+ $dt['year'] = isset($dt['year']) ? (int)$dt['year'] : 0;
+ $dt['month'] = isset($dt['month']) ? (int)$dt['month'] : 0;
+ $dt['day'] = isset($dt['day']) ? (int)$dt['day'] : 0;
+ $dt['hour'] = isset($dt['hour']) ? (int)$dt['hour'] : 0;
+ $dt['minute'] = isset($dt['minute']) ? (int)$dt['minute'] : 0;
+ $dt['second'] = isset($dt['second']) ? (int)$dt['second'] : 0;
+
+ return $dt;
+ }
+
+ /**
+ * @param $format
+ * @param $str
+ * @param null $timezone
+ * @return \DateTime
+ */
+ public static function createDatetimeFromFormat($format, $str, $timezone = null)
+ {
+ $pd = self::parseFromFormat($format, $str);
+ $gd = self::toGregorian($pd['year'], $pd['month'], $pd['day']);
+ $date = self::createDateTime('now', $timezone);
+ $date->setDate($gd[0], $gd[1], $gd[2]);
+ $date->setTime($pd['hour'], $pd['minute'], $pd['second']);
+
+ return $date;
+ }
+
+ /**
+ * @param $format
+ * @param $str
+ * @param null $timezone
+ * @return Carbon
+ */
+ public static function createCarbonFromFormat($format, $str, $timezone = null)
+ {
+ $dateTime = self::createDatetimeFromFormat($format, $str, $timezone);
+
+ return Carbon::createFromTimestamp($dateTime->getTimestamp(), $dateTime->getTimezone());
+ }
+
+ /**
+ * Convert Latin numbers to persian numbers
+ *
+ * @param string $string
+ * @return string
+ */
+ public static function convertNumbers($string)
+ {
+ $farsi_array = array("۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹");
+ $english_array = array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
+
+ return str_replace($english_array, $farsi_array, $string);
+ }
+
+ /**
+ * @param $timestamp
+ * @param null $timezone
+ * @return \DateTime|static
+ */
+ public static function createDateTime($timestamp = null, $timezone = null)
+ {
+ $timezone = static::createTimeZone($timezone);
+
+ if ($timestamp === null) {
+ return Carbon::now($timezone);
+ }
+
+
+ if ($timestamp instanceof \DateTimeInterface) {
+ return $timestamp;
+ }
+
+ if (is_string($timestamp)) {
+ return new \DateTime($timestamp, $timezone);
+ }
+
+ if (is_numeric($timestamp)) {
+ return Carbon::createFromTimestamp($timestamp, $timezone);
+ }
+
+
+ throw new \InvalidArgumentException('timestamp is not valid');
+ }
+
+ /**
+ * @param null $timezone
+ * @return \DateTimeZone|null
+ */
+ public static function createTimeZone($timezone = null)
+ {
+ if ($timezone instanceof \DateTimeZone) {
+ return $timezone;
+ }
+
+ if ($timezone === null) {
+ return new \DateTimeZone(date_default_timezone_get());
+ }
+
+ if (is_string($timezone)) {
+ return new \DateTimeZone($timezone);
+ }
+
+
+ throw new \InvalidArgumentException('timezone is not valid');
+
+ }
+}
diff --git a/core/vendor/morilog/jalali/tests/HelperTest.php b/core/vendor/morilog/jalali/tests/HelperTest.php
new file mode 100644
index 0000000..1c994a9
--- /dev/null
+++ b/core/vendor/morilog/jalali/tests/HelperTest.php
@@ -0,0 +1,12 @@
+assertTrue(function_exists('jdate'));
+
+ $jdate = jdate('now');
+ $this->assertTrue($jdate instanceof \Morilog\Jalali\jDate);
+ }
+}
diff --git a/core/vendor/morilog/jalali/tests/jDateTest.php b/core/vendor/morilog/jalali/tests/jDateTest.php
new file mode 100644
index 0000000..2769682
--- /dev/null
+++ b/core/vendor/morilog/jalali/tests/jDateTest.php
@@ -0,0 +1,58 @@
+assertTrue(is_a($object, jDate::class));
+ }
+
+ public function test_it_must_foregable_and_must_work_fine()
+ {
+ $object = new jDate();
+ $jDate = $object->forge('2015-06-03')->format('Y-m-d');
+
+ $this->assertNotNull($object->forge());
+ $this->assertTrue('1394-03-13' === $jDate);
+ }
+
+ public function test_it_must_reforgable_most_work_fine()
+ {
+ $object = new jDate();
+ $jDate = $object->forge('2015-06-03')
+ ->reforge('+ 3 days')
+ ->format('Y-m-d');
+
+ $this->assertTrue('1394-03-16' === $jDate);
+ }
+
+ public function test_relative_time()
+ {
+ $object = new jDate();
+ $jDate = $object->forge('- 10 minutes')->ago();
+
+ $this->assertTrue('10 دقیقه پیش' === $jDate);
+ }
+
+ public function test_format_with_convert_to_persian()
+ {
+ $object = new jDate();
+ $jDate = $object->forge('2015-06-13')->format('Y-m-d');
+
+ $this->assertTrue('۱۳۹۴-۰۳-۲۳' === \Morilog\Jalali\jDateTime::convertNumbers($jDate));
+ }
+
+ public function test_time()
+ {
+ $time = time();
+ $theTime = \Morilog\Jalali\jDate::forge($time)->time();
+
+ $this->assertTrue($time === $theTime);
+
+ $theTime = \Morilog\Jalali\jDate::forge('now')->time();
+ $this->assertTrue($theTime === strtotime('now'));
+ }
+}
diff --git a/core/vendor/morilog/jalali/tests/jDateTimeTest.php b/core/vendor/morilog/jalali/tests/jDateTimeTest.php
new file mode 100644
index 0000000..298fefe
--- /dev/null
+++ b/core/vendor/morilog/jalali/tests/jDateTimeTest.php
@@ -0,0 +1,117 @@
+assertTrue(jDateTime::checkDate(1391, 2, 30, true));
+ $this->assertFalse(jDateTime::checkDate(1395, 13, 10, true));
+ $this->assertFalse(jDateTime::checkDate(1395, 12, 31, true));
+ $this->assertFalse(jDateTime::checkDate(2015, 12, 31, true));
+ }
+
+ public function testToJalali()
+ {
+ $this->assertTrue(jDateTime::toJalali(2016, 5, 7) === [1395, 2, 18]);
+ $this->assertFalse(jDateTime::toJalali(2015, 5, 7) === [1394, 2, 18]);
+ }
+
+ public function testToGregorian()
+ {
+ $this->assertTrue(jDateTime::toGregorian(1395, 2, 18) === [2016, 5, 7]);
+ $this->assertFalse(jDateTime::toGregorian(1394, 2, 18) === [2015, 5, 7]);
+ }
+
+ public function testIsLeapJalaliYear()
+ {
+ $this->assertTrue(jDateTime::isLeapJalaliYear(1395));
+ $this->assertFalse(jDateTime::isLeapJalaliYear(1394));
+ }
+
+ public function testStrftime()
+ {
+ $this->assertTrue(jDateTime::strftime('Y-m-d', strtotime('2016-05-8')) === '1395-02-19');
+ $this->assertTrue(jDateTime::convertNumbers(jDateTime::strftime('Y-m-d',
+ strtotime('2016-05-8'))) === '۱۳۹۵-۰۲-۱۹');
+ $this->assertFalse(jDateTime::strftime('Y-m-d', strtotime('2016-05-8')) === '۱۳۹۵-۰۲-۱۹');
+ }
+
+ public function test_parseFromPersian()
+ {
+ $jalaliDate = '1393/03/27';
+ $date = jDateTime::parseFromFormat('Y/m/d', $jalaliDate);
+
+ $this->assertEquals(1393, $date['year']);
+ $this->assertEquals(03, $date['month']);
+ $this->assertEquals(27, $date['day']);
+
+ $date = jDateTime::parseFromFormat('Y-m-d H:i:s', '1395-03-15 21:00:00');
+ $this->assertEquals(21, $date['hour']);
+ $this->assertEquals(0, $date['minute']);
+ $this->assertEquals(0, $date['second']);
+ }
+
+ public function testCreateDateTimeFormFormat()
+ {
+ $jdate = '1394/11/25 15:00:00';
+ $gDateTime = jDatetime::createDatetimeFromFormat('Y/m/d H:i:s', $jdate);
+
+ $this->assertTrue($gDateTime instanceof \DateTime);
+
+ $this->assertTrue('2016-02-14 15:00:00' === $gDateTime->format('Y-m-d H:i:s'));
+ }
+
+ public function testCreateCarbonFormFormat()
+ {
+ $jdate = '1394/11/25 15:00:00';
+ $carbon = jDatetime::createCarbonFromFormat('Y/m/d H:i:s', $jdate);
+
+ $this->assertTrue($carbon instanceof \Carbon\Carbon);
+ $this->assertTrue($carbon->day === 14);
+ $this->assertTrue('2016-02-14 15:00:00' === $carbon->format('Y-m-d H:i:s'));
+
+ $jalaiDateFormatted = jDate::forge($carbon->toDateString())->format('Y-m-d H:i:s');
+ $jalaiDateTimeFormatted = jDate::forge($carbon->toDateTimeString())->format('Y-m-d H:i:s');
+ $this->assertFalse($jalaiDateFormatted === '1394-11-25 15:00:00');
+ $this->assertTrue($jalaiDateTimeFormatted === '1394-11-25 15:00:00');
+
+ }
+
+ public function testTimezone()
+ {
+ date_default_timezone_set('Asia/Tehran');
+ $tehranDate = jDate::forge();
+ $tehranHour = $tehranDate->format('H');
+ $tehranMin = $tehranDate->format('i');
+
+ date_default_timezone_set('UTC');
+ $utcDate = jDate::forge();
+ $utcHour = $utcDate->format('H');
+ $utcMin = $utcDate->format('i');
+
+ $tzOffset = $this->getTimeZoneOffset('Asia/Tehran', 'UTC');
+
+ $this->assertTrue((((($utcHour * 60) + $utcMin) * 60) - ((($tehranHour * 60) + $tehranMin) * 60)) === $tzOffset);
+
+ }
+
+
+ private function getTimeZoneOffset($remote_tz, $origin_tz = null)
+ {
+ if ($origin_tz === null) {
+ if (!is_string($origin_tz = date_default_timezone_get())) {
+ return false; // A UTC timestamp was returned -- bail out!
+ }
+ }
+ $origin_dtz = new DateTimeZone($origin_tz);
+ $remote_dtz = new DateTimeZone($remote_tz);
+ $origin_dt = new DateTime("now", $origin_dtz);
+ $remote_dt = new DateTime("now", $remote_dtz);
+ $offset = $origin_dtz->getOffset($origin_dt) - $remote_dtz->getOffset($remote_dt);
+
+ return $offset;
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/.editorconfig b/core/vendor/mtdowling/cron-expression/.editorconfig
new file mode 100644
index 0000000..1492202
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/.editorconfig
@@ -0,0 +1,16 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.yml]
+indent_style = space
+indent_size = 2
diff --git a/core/vendor/mtdowling/cron-expression/CHANGELOG.md b/core/vendor/mtdowling/cron-expression/CHANGELOG.md
new file mode 100644
index 0000000..8ddab90
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/CHANGELOG.md
@@ -0,0 +1,36 @@
+# Change Log
+
+## [1.2.0] - 2017-01-22
+### Added
+- Added IDE, CodeSniffer, and StyleCI.IO support
+
+### Changed
+- Switched to PSR-4 Autoloading
+
+### Fixed
+- 0 step expressions are handled better
+- Fixed `DayOfMonth` validation to be more strict
+- Typos
+
+## [1.1.0] - 2016-01-26
+### Added
+- Support for non-hourly offset timezones
+- Checks for valid expressions
+
+### Changed
+- Max Iterations no longer hardcoded for `getRunDate()`
+- Supports DateTimeImmutable for newer PHP verions
+
+### Fixed
+- Fixed looping bug for PHP 7 when determining the last specified weekday of a month
+
+## [1.0.3] - 2013-11-23
+### Added
+- Now supports expressions with any number of extra spaces, tabs, or newlines
+
+### Changed
+- Using static instead of self in `CronExpression::factory`
+
+### Fixed
+- Fixes issue [#28](https://github.com/mtdowling/cron-expression/issues/28) where PHP increments of ranges were failing due to PHP casting hyphens to 0
+- Only set default timezone if the given $currentTime is not a DateTime instance ([#34](https://github.com/mtdowling/cron-expression/issues/34))
diff --git a/core/vendor/mtdowling/cron-expression/LICENSE b/core/vendor/mtdowling/cron-expression/LICENSE
new file mode 100644
index 0000000..c6d88ac
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Michael Dowling and contributors
+
+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/core/vendor/mtdowling/cron-expression/README.md b/core/vendor/mtdowling/cron-expression/README.md
new file mode 100644
index 0000000..c9e3bf3
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/README.md
@@ -0,0 +1,71 @@
+PHP Cron Expression Parser
+==========================
+
+[![Latest Stable Version](https://poser.pugx.org/mtdowling/cron-expression/v/stable.png)](https://packagist.org/packages/mtdowling/cron-expression) [![Total Downloads](https://poser.pugx.org/mtdowling/cron-expression/downloads.png)](https://packagist.org/packages/mtdowling/cron-expression) [![Build Status](https://secure.travis-ci.org/mtdowling/cron-expression.png)](http://travis-ci.org/mtdowling/cron-expression)
+
+The PHP cron expression parser can parse a CRON expression, determine if it is
+due to run, calculate the next run date of the expression, and calculate the previous
+run date of the expression. You can calculate dates far into the future or past by
+skipping n number of matching dates.
+
+The parser can handle increments of ranges (e.g. */12, 2-59/3), intervals (e.g. 0-9),
+lists (e.g. 1,2,3), W to find the nearest weekday for a given day of the month, L to
+find the last day of the month, L to find the last given weekday of a month, and hash
+(#) to find the nth weekday of a given month.
+
+Installing
+==========
+
+Add the dependency to your project:
+
+```bash
+composer require mtdowling/cron-expression
+```
+
+Usage
+=====
+```php
+isDue();
+echo $cron->getNextRunDate()->format('Y-m-d H:i:s');
+echo $cron->getPreviousRunDate()->format('Y-m-d H:i:s');
+
+// Works with complex expressions
+$cron = Cron\CronExpression::factory('3-59/15 2,6-12 */15 1 2-5');
+echo $cron->getNextRunDate()->format('Y-m-d H:i:s');
+
+// Calculate a run date two iterations into the future
+$cron = Cron\CronExpression::factory('@daily');
+echo $cron->getNextRunDate(null, 2)->format('Y-m-d H:i:s');
+
+// Calculate a run date relative to a specific time
+$cron = Cron\CronExpression::factory('@monthly');
+echo $cron->getNextRunDate('2010-01-12 00:00:00')->format('Y-m-d H:i:s');
+```
+
+CRON Expressions
+================
+
+A CRON expression is a string representing the schedule for a particular command to execute. The parts of a CRON schedule are as follows:
+
+ * * * * * *
+ - - - - - -
+ | | | | | |
+ | | | | | + year [optional]
+ | | | | +----- day of week (0 - 7) (Sunday=0 or 7)
+ | | | +---------- month (1 - 12)
+ | | +--------------- day of month (1 - 31)
+ | +-------------------- hour (0 - 23)
+ +------------------------- min (0 - 59)
+
+Requirements
+============
+
+- PHP 5.3+
+- PHPUnit is required to run the unit tests
+- Composer is required to run the unit tests
\ No newline at end of file
diff --git a/core/vendor/mtdowling/cron-expression/composer.json b/core/vendor/mtdowling/cron-expression/composer.json
new file mode 100644
index 0000000..ce42d40
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "mtdowling/cron-expression",
+ "type": "library",
+ "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
+ "keywords": ["cron", "schedule"],
+ "license": "MIT",
+ "authors": [{
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }],
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0|~5.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Cron\\": "src/Cron/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests/Cron/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/AbstractField.php b/core/vendor/mtdowling/cron-expression/src/Cron/AbstractField.php
new file mode 100644
index 0000000..cd2410a
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/AbstractField.php
@@ -0,0 +1,148 @@
+isIncrementsOfRanges($value)) {
+ return $this->isInIncrementsOfRanges($dateValue, $value);
+ } elseif ($this->isRange($value)) {
+ return $this->isInRange($dateValue, $value);
+ }
+
+ return $value == '*' || $dateValue == $value;
+ }
+
+ /**
+ * Check if a value is a range
+ *
+ * @param string $value Value to test
+ *
+ * @return bool
+ */
+ public function isRange($value)
+ {
+ return strpos($value, '-') !== false;
+ }
+
+ /**
+ * Check if a value is an increments of ranges
+ *
+ * @param string $value Value to test
+ *
+ * @return bool
+ */
+ public function isIncrementsOfRanges($value)
+ {
+ return strpos($value, '/') !== false;
+ }
+
+ /**
+ * Test if a value is within a range
+ *
+ * @param string $dateValue Set date value
+ * @param string $value Value to test
+ *
+ * @return bool
+ */
+ public function isInRange($dateValue, $value)
+ {
+ $parts = array_map('trim', explode('-', $value, 2));
+
+ return $dateValue >= $parts[0] && $dateValue <= $parts[1];
+ }
+
+ /**
+ * Test if a value is within an increments of ranges (offset[-to]/step size)
+ *
+ * @param string $dateValue Set date value
+ * @param string $value Value to test
+ *
+ * @return bool
+ */
+ public function isInIncrementsOfRanges($dateValue, $value)
+ {
+ $parts = array_map('trim', explode('/', $value, 2));
+ $stepSize = isset($parts[1]) ? (int) $parts[1] : 0;
+
+ if ($stepSize === 0) {
+ return false;
+ }
+
+ if (($parts[0] == '*' || $parts[0] === '0')) {
+ return (int) $dateValue % $stepSize == 0;
+ }
+
+ $range = explode('-', $parts[0], 2);
+ $offset = $range[0];
+ $to = isset($range[1]) ? $range[1] : $dateValue;
+ // Ensure that the date value is within the range
+ if ($dateValue < $offset || $dateValue > $to) {
+ return false;
+ }
+
+ if ($dateValue > $offset && 0 === $stepSize) {
+ return false;
+ }
+
+ for ($i = $offset; $i <= $to; $i+= $stepSize) {
+ if ($i == $dateValue) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a range of values for the given cron expression
+ *
+ * @param string $expression The expression to evaluate
+ * @param int $max Maximum offset for range
+ *
+ * @return array
+ */
+ public function getRangeForExpression($expression, $max)
+ {
+ $values = array();
+
+ if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) {
+ if (!$this->isIncrementsOfRanges($expression)) {
+ list ($offset, $to) = explode('-', $expression);
+ $stepSize = 1;
+ }
+ else {
+ $range = array_map('trim', explode('/', $expression, 2));
+ $stepSize = isset($range[1]) ? $range[1] : 0;
+ $range = $range[0];
+ $range = explode('-', $range, 2);
+ $offset = $range[0];
+ $to = isset($range[1]) ? $range[1] : $max;
+ }
+ $offset = $offset == '*' ? 0 : $offset;
+ for ($i = $offset; $i <= $to; $i += $stepSize) {
+ $values[] = $i;
+ }
+ sort($values);
+ }
+ else {
+ $values = array($expression);
+ }
+
+ return $values;
+ }
+
+}
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/CronExpression.php b/core/vendor/mtdowling/cron-expression/src/Cron/CronExpression.php
new file mode 100644
index 0000000..d69b415
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/CronExpression.php
@@ -0,0 +1,389 @@
+ '0 0 1 1 *',
+ '@annually' => '0 0 1 1 *',
+ '@monthly' => '0 0 1 * *',
+ '@weekly' => '0 0 * * 0',
+ '@daily' => '0 0 * * *',
+ '@hourly' => '0 * * * *'
+ );
+
+ if (isset($mappings[$expression])) {
+ $expression = $mappings[$expression];
+ }
+
+ return new static($expression, $fieldFactory ?: new FieldFactory());
+ }
+
+ /**
+ * Validate a CronExpression.
+ *
+ * @param string $expression The CRON expression to validate.
+ *
+ * @return bool True if a valid CRON expression was passed. False if not.
+ * @see \Cron\CronExpression::factory
+ */
+ public static function isValidExpression($expression)
+ {
+ try {
+ self::factory($expression);
+ } catch (InvalidArgumentException $e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Parse a CRON expression
+ *
+ * @param string $expression CRON expression (e.g. '8 * * * *')
+ * @param FieldFactory $fieldFactory Factory to create cron fields
+ */
+ public function __construct($expression, FieldFactory $fieldFactory)
+ {
+ $this->fieldFactory = $fieldFactory;
+ $this->setExpression($expression);
+ }
+
+ /**
+ * Set or change the CRON expression
+ *
+ * @param string $value CRON expression (e.g. 8 * * * *)
+ *
+ * @return CronExpression
+ * @throws \InvalidArgumentException if not a valid CRON expression
+ */
+ public function setExpression($value)
+ {
+ $this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
+ if (count($this->cronParts) < 5) {
+ throw new InvalidArgumentException(
+ $value . ' is not a valid CRON expression'
+ );
+ }
+
+ foreach ($this->cronParts as $position => $part) {
+ $this->setPart($position, $part);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set part of the CRON expression
+ *
+ * @param int $position The position of the CRON expression to set
+ * @param string $value The value to set
+ *
+ * @return CronExpression
+ * @throws \InvalidArgumentException if the value is not valid for the part
+ */
+ public function setPart($position, $value)
+ {
+ if (!$this->fieldFactory->getField($position)->validate($value)) {
+ throw new InvalidArgumentException(
+ 'Invalid CRON field value ' . $value . ' at position ' . $position
+ );
+ }
+
+ $this->cronParts[$position] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set max iteration count for searching next run dates
+ *
+ * @param int $maxIterationCount Max iteration count when searching for next run date
+ *
+ * @return CronExpression
+ */
+ public function setMaxIterationCount($maxIterationCount)
+ {
+ $this->maxIterationCount = $maxIterationCount;
+
+ return $this;
+ }
+
+ /**
+ * Get a next run date relative to the current date or a specific date
+ *
+ * @param string|\DateTime $currentTime Relative calculation date
+ * @param int $nth Number of matches to skip before returning a
+ * matching next run date. 0, the default, will return the current
+ * date and time if the next run date falls on the current date and
+ * time. Setting this value to 1 will skip the first match and go to
+ * the second match. Setting this value to 2 will skip the first 2
+ * matches and so on.
+ * @param bool $allowCurrentDate Set to TRUE to return the current date if
+ * it matches the cron expression.
+ *
+ * @return \DateTime
+ * @throws \RuntimeException on too many iterations
+ */
+ public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
+ {
+ return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate);
+ }
+
+ /**
+ * Get a previous run date relative to the current date or a specific date
+ *
+ * @param string|\DateTime $currentTime Relative calculation date
+ * @param int $nth Number of matches to skip before returning
+ * @param bool $allowCurrentDate Set to TRUE to return the
+ * current date if it matches the cron expression
+ *
+ * @return \DateTime
+ * @throws \RuntimeException on too many iterations
+ * @see \Cron\CronExpression::getNextRunDate
+ */
+ public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
+ {
+ return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate);
+ }
+
+ /**
+ * Get multiple run dates starting at the current date or a specific date
+ *
+ * @param int $total Set the total number of dates to calculate
+ * @param string|\DateTime $currentTime Relative calculation date
+ * @param bool $invert Set to TRUE to retrieve previous dates
+ * @param bool $allowCurrentDate Set to TRUE to return the
+ * current date if it matches the cron expression
+ *
+ * @return array Returns an array of run dates
+ */
+ public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false)
+ {
+ $matches = array();
+ for ($i = 0; $i < max(0, $total); $i++) {
+ try {
+ $matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate);
+ } catch (RuntimeException $e) {
+ break;
+ }
+ }
+
+ return $matches;
+ }
+
+ /**
+ * Get all or part of the CRON expression
+ *
+ * @param string $part Specify the part to retrieve or NULL to get the full
+ * cron schedule string.
+ *
+ * @return string|null Returns the CRON expression, a part of the
+ * CRON expression, or NULL if the part was specified but not found
+ */
+ public function getExpression($part = null)
+ {
+ if (null === $part) {
+ return implode(' ', $this->cronParts);
+ } elseif (array_key_exists($part, $this->cronParts)) {
+ return $this->cronParts[$part];
+ }
+
+ return null;
+ }
+
+ /**
+ * Helper method to output the full expression.
+ *
+ * @return string Full CRON expression
+ */
+ public function __toString()
+ {
+ return $this->getExpression();
+ }
+
+ /**
+ * Determine if the cron is due to run based on the current date or a
+ * specific date. This method assumes that the current number of
+ * seconds are irrelevant, and should be called once per minute.
+ *
+ * @param string|\DateTime $currentTime Relative calculation date
+ *
+ * @return bool Returns TRUE if the cron is due to run or FALSE if not
+ */
+ public function isDue($currentTime = 'now')
+ {
+ if ('now' === $currentTime) {
+ $currentDate = date('Y-m-d H:i');
+ $currentTime = strtotime($currentDate);
+ } elseif ($currentTime instanceof DateTime) {
+ $currentDate = clone $currentTime;
+ // Ensure time in 'current' timezone is used
+ $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
+ $currentDate = $currentDate->format('Y-m-d H:i');
+ $currentTime = strtotime($currentDate);
+ } elseif ($currentTime instanceof DateTimeImmutable) {
+ $currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
+ $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
+ $currentDate = $currentDate->format('Y-m-d H:i');
+ $currentTime = strtotime($currentDate);
+ } else {
+ $currentTime = new DateTime($currentTime);
+ $currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0);
+ $currentDate = $currentTime->format('Y-m-d H:i');
+ $currentTime = $currentTime->getTimeStamp();
+ }
+
+ try {
+ return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime;
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Get the next or previous run date of the expression relative to a date
+ *
+ * @param string|\DateTime $currentTime Relative calculation date
+ * @param int $nth Number of matches to skip before returning
+ * @param bool $invert Set to TRUE to go backwards in time
+ * @param bool $allowCurrentDate Set to TRUE to return the
+ * current date if it matches the cron expression
+ *
+ * @return \DateTime
+ * @throws \RuntimeException on too many iterations
+ */
+ protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false)
+ {
+ if ($currentTime instanceof DateTime) {
+ $currentDate = clone $currentTime;
+ } elseif ($currentTime instanceof DateTimeImmutable) {
+ $currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
+ $currentDate->setTimezone($currentTime->getTimezone());
+ } else {
+ $currentDate = new DateTime($currentTime ?: 'now');
+ $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
+ }
+
+ $currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0);
+ $nextRun = clone $currentDate;
+ $nth = (int) $nth;
+
+ // We don't have to satisfy * or null fields
+ $parts = array();
+ $fields = array();
+ foreach (self::$order as $position) {
+ $part = $this->getExpression($position);
+ if (null === $part || '*' === $part) {
+ continue;
+ }
+ $parts[$position] = $part;
+ $fields[$position] = $this->fieldFactory->getField($position);
+ }
+
+ // Set a hard limit to bail on an impossible date
+ for ($i = 0; $i < $this->maxIterationCount; $i++) {
+
+ foreach ($parts as $position => $part) {
+ $satisfied = false;
+ // Get the field object used to validate this part
+ $field = $fields[$position];
+ // Check if this is singular or a list
+ if (strpos($part, ',') === false) {
+ $satisfied = $field->isSatisfiedBy($nextRun, $part);
+ } else {
+ foreach (array_map('trim', explode(',', $part)) as $listPart) {
+ if ($field->isSatisfiedBy($nextRun, $listPart)) {
+ $satisfied = true;
+ break;
+ }
+ }
+ }
+
+ // If the field is not satisfied, then start over
+ if (!$satisfied) {
+ $field->increment($nextRun, $invert, $part);
+ continue 2;
+ }
+ }
+
+ // Skip this match if needed
+ if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
+ $this->fieldFactory->getField(0)->increment($nextRun, $invert, isset($parts[0]) ? $parts[0] : null);
+ continue;
+ }
+
+ return $nextRun;
+ }
+
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('Impossible CRON expression');
+ // @codeCoverageIgnoreEnd
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php b/core/vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php
new file mode 100644
index 0000000..53e15bc
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php
@@ -0,0 +1,173 @@
+
+ */
+class DayOfMonthField extends AbstractField
+{
+ /**
+ * Get the nearest day of the week for a given day in a month
+ *
+ * @param int $currentYear Current year
+ * @param int $currentMonth Current month
+ * @param int $targetDay Target day of the month
+ *
+ * @return \DateTime Returns the nearest date
+ */
+ private static function getNearestWeekday($currentYear, $currentMonth, $targetDay)
+ {
+ $tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT);
+ $target = DateTime::createFromFormat('Y-m-d', "$currentYear-$currentMonth-$tday");
+ $currentWeekday = (int) $target->format('N');
+
+ if ($currentWeekday < 6) {
+ return $target;
+ }
+
+ $lastDayOfMonth = $target->format('t');
+
+ foreach (array(-1, 1, -2, 2) as $i) {
+ $adjusted = $targetDay + $i;
+ if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
+ $target->setDate($currentYear, $currentMonth, $adjusted);
+ if ($target->format('N') < 6 && $target->format('m') == $currentMonth) {
+ return $target;
+ }
+ }
+ }
+ }
+
+ public function isSatisfiedBy(DateTime $date, $value)
+ {
+ // ? states that the field value is to be skipped
+ if ($value == '?') {
+ return true;
+ }
+
+ $fieldValue = $date->format('d');
+
+ // Check to see if this is the last day of the month
+ if ($value == 'L') {
+ return $fieldValue == $date->format('t');
+ }
+
+ // Check to see if this is the nearest weekday to a particular value
+ if (strpos($value, 'W')) {
+ // Parse the target day
+ $targetDay = substr($value, 0, strpos($value, 'W'));
+ // Find out if the current day is the nearest day of the week
+ return $date->format('j') == self::getNearestWeekday(
+ $date->format('Y'),
+ $date->format('m'),
+ $targetDay
+ )->format('j');
+ }
+
+ return $this->isSatisfied($date->format('d'), $value);
+ }
+
+ public function increment(DateTime $date, $invert = false)
+ {
+ if ($invert) {
+ $date->modify('previous day');
+ $date->setTime(23, 59);
+ } else {
+ $date->modify('next day');
+ $date->setTime(0, 0);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validates that the value is valid for the Day of the Month field
+ * Days of the month can contain values of 1-31, *, L, or ? by default. This can be augmented with lists via a ',',
+ * ranges via a '-', or with a '[0-9]W' to specify the closest weekday.
+ *
+ * @param string $value
+ * @return bool
+ */
+ public function validate($value)
+ {
+ // Allow wildcards and a single L
+ if ($value === '?' || $value === '*' || $value === 'L') {
+ return true;
+ }
+
+ // If you only contain numbers and are within 1-31
+ if ((bool) preg_match('/^\d{1,2}$/', $value) && ($value >= 1 && $value <= 31)) {
+ return true;
+ }
+
+ // If you have a -, we will deal with each of your chunks
+ if ((bool) preg_match('/-/', $value)) {
+ // We cannot have a range within a list or vice versa
+ if ((bool) preg_match('/,/', $value)) {
+ return false;
+ }
+
+ $chunks = explode('-', $value);
+ foreach ($chunks as $chunk) {
+ if (!$this->validate($chunk)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // If you have a comma, we will deal with each value
+ if ((bool) preg_match('/,/', $value)) {
+ // We cannot have a range within a list or vice versa
+ if ((bool) preg_match('/-/', $value)) {
+ return false;
+ }
+
+ $chunks = explode(',', $value);
+ foreach ($chunks as $chunk) {
+ if (!$this->validate($chunk)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // If you contain a /, we'll deal with it
+ if ((bool) preg_match('/\//', $value)) {
+ $chunks = explode('/', $value);
+ foreach ($chunks as $chunk) {
+ if (!$this->validate($chunk)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // If you end in W, make sure that it has a numeric in front of it
+ if ((bool) preg_match('/^\d{1,2}W$/', $value)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php b/core/vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php
new file mode 100644
index 0000000..83e2f4c
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php
@@ -0,0 +1,141 @@
+convertLiterals($value);
+
+ $currentYear = $date->format('Y');
+ $currentMonth = $date->format('m');
+ $lastDayOfMonth = $date->format('t');
+
+ // Find out if this is the last specific weekday of the month
+ if (strpos($value, 'L')) {
+ $weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L')));
+ $tdate = clone $date;
+ $tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth);
+ while ($tdate->format('w') != $weekday) {
+ $tdateClone = new DateTime();
+ $tdate = $tdateClone
+ ->setTimezone($tdate->getTimezone())
+ ->setDate($currentYear, $currentMonth, --$lastDayOfMonth);
+ }
+
+ return $date->format('j') == $lastDayOfMonth;
+ }
+
+ // Handle # hash tokens
+ if (strpos($value, '#')) {
+ list($weekday, $nth) = explode('#', $value);
+
+ // 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601
+ if ($weekday === '0') {
+ $weekday = 7;
+ }
+
+ // Validate the hash fields
+ if ($weekday < 0 || $weekday > 7) {
+ throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given");
+ }
+ if ($nth > 5) {
+ throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month');
+ }
+ // The current weekday must match the targeted weekday to proceed
+ if ($date->format('N') != $weekday) {
+ return false;
+ }
+
+ $tdate = clone $date;
+ $tdate->setDate($currentYear, $currentMonth, 1);
+ $dayCount = 0;
+ $currentDay = 1;
+ while ($currentDay < $lastDayOfMonth + 1) {
+ if ($tdate->format('N') == $weekday) {
+ if (++$dayCount >= $nth) {
+ break;
+ }
+ }
+ $tdate->setDate($currentYear, $currentMonth, ++$currentDay);
+ }
+
+ return $date->format('j') == $currentDay;
+ }
+
+ // Handle day of the week values
+ if (strpos($value, '-')) {
+ $parts = explode('-', $value);
+ if ($parts[0] == '7') {
+ $parts[0] = '0';
+ } elseif ($parts[1] == '0') {
+ $parts[1] = '7';
+ }
+ $value = implode('-', $parts);
+ }
+
+ // Test to see which Sunday to use -- 0 == 7 == Sunday
+ $format = in_array(7, str_split($value)) ? 'N' : 'w';
+ $fieldValue = $date->format($format);
+
+ return $this->isSatisfied($fieldValue, $value);
+ }
+
+ public function increment(DateTime $date, $invert = false)
+ {
+ if ($invert) {
+ $date->modify('-1 day');
+ $date->setTime(23, 59, 0);
+ } else {
+ $date->modify('+1 day');
+ $date->setTime(0, 0, 0);
+ }
+
+ return $this;
+ }
+
+ public function validate($value)
+ {
+ $value = $this->convertLiterals($value);
+
+ foreach (explode(',', $value) as $expr) {
+ if (!preg_match('/^(\*|[0-7](L?|#[1-5]))([\/\,\-][0-7]+)*$/', $expr)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private function convertLiterals($string)
+ {
+ return str_ireplace(
+ array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'),
+ range(0, 6),
+ $string
+ );
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php b/core/vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php
new file mode 100644
index 0000000..fa0e6fe
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php
@@ -0,0 +1,57 @@
+fields[$position])) {
+ switch ($position) {
+ case 0:
+ $this->fields[$position] = new MinutesField();
+ break;
+ case 1:
+ $this->fields[$position] = new HoursField();
+ break;
+ case 2:
+ $this->fields[$position] = new DayOfMonthField();
+ break;
+ case 3:
+ $this->fields[$position] = new MonthField();
+ break;
+ case 4:
+ $this->fields[$position] = new DayOfWeekField();
+ break;
+ case 5:
+ $this->fields[$position] = new YearField();
+ break;
+ default:
+ throw new InvalidArgumentException(
+ $position . ' is not a valid position'
+ );
+ }
+ }
+
+ return $this->fields[$position];
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php b/core/vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php
new file mode 100644
index 0000000..be37b93
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php
@@ -0,0 +1,40 @@
+isSatisfied($date->format('H'), $value);
+ }
+
+ public function increment(DateTime $date, $invert = false, $parts = null)
+ {
+ // Change timezone to UTC temporarily. This will
+ // allow us to go back or forwards and hour even
+ // if DST will be changed between the hours.
+ if (is_null($parts) || $parts == '*') {
+ $timezone = $date->getTimezone();
+ $date->setTimezone(new DateTimeZone('UTC'));
+ if ($invert) {
+ $date->modify('-1 hour');
+ } else {
+ $date->modify('+1 hour');
+ }
+ $date->setTimezone($timezone);
+
+ $date->setTime($date->format('H'), $invert ? 59 : 0);
+ return $this;
+ }
+
+ $parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
+ $hours = array();
+ foreach ($parts as $part) {
+ $hours = array_merge($hours, $this->getRangeForExpression($part, 23));
+ }
+
+ $current_hour = $date->format('H');
+ $position = $invert ? count($hours) - 1 : 0;
+ if (count($hours) > 1) {
+ for ($i = 0; $i < count($hours) - 1; $i++) {
+ if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) ||
+ ($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) {
+ $position = $invert ? $i : $i + 1;
+ break;
+ }
+ }
+ }
+
+ $hour = $hours[$position];
+ if ((!$invert && $date->format('H') >= $hour) || ($invert && $date->format('H') <= $hour)) {
+ $date->modify(($invert ? '-' : '+') . '1 day');
+ $date->setTime($invert ? 23 : 0, $invert ? 59 : 0);
+ }
+ else {
+ $date->setTime($hour, $invert ? 59 : 0);
+ }
+
+ return $this;
+ }
+
+ public function validate($value)
+ {
+ return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/MinutesField.php b/core/vendor/mtdowling/cron-expression/src/Cron/MinutesField.php
new file mode 100644
index 0000000..d8432b5
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/MinutesField.php
@@ -0,0 +1,62 @@
+isSatisfied($date->format('i'), $value);
+ }
+
+ public function increment(DateTime $date, $invert = false, $parts = null)
+ {
+ if (is_null($parts)) {
+ if ($invert) {
+ $date->modify('-1 minute');
+ } else {
+ $date->modify('+1 minute');
+ }
+ return $this;
+ }
+
+ $parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
+ $minutes = array();
+ foreach ($parts as $part) {
+ $minutes = array_merge($minutes, $this->getRangeForExpression($part, 59));
+ }
+
+ $current_minute = $date->format('i');
+ $position = $invert ? count($minutes) - 1 : 0;
+ if (count($minutes) > 1) {
+ for ($i = 0; $i < count($minutes) - 1; $i++) {
+ if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) ||
+ ($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) {
+ $position = $invert ? $i : $i + 1;
+ break;
+ }
+ }
+ }
+
+ if ((!$invert && $current_minute >= $minutes[$position]) || ($invert && $current_minute <= $minutes[$position])) {
+ $date->modify(($invert ? '-' : '+') . '1 hour');
+ $date->setTime($date->format('H'), $invert ? 59 : 0);
+ }
+ else {
+ $date->setTime($date->format('H'), $minutes[$position]);
+ }
+
+ return $this;
+ }
+
+ public function validate($value)
+ {
+ return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/MonthField.php b/core/vendor/mtdowling/cron-expression/src/Cron/MonthField.php
new file mode 100644
index 0000000..0205c17
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/MonthField.php
@@ -0,0 +1,44 @@
+isSatisfied($date->format('m'), $value);
+ }
+
+ public function increment(DateTime $date, $invert = false)
+ {
+ if ($invert) {
+ $date->modify('last day of previous month');
+ $date->setTime(23, 59);
+ } else {
+ $date->modify('first day of next month');
+ $date->setTime(0, 0);
+ }
+
+ return $this;
+ }
+
+ public function validate($value)
+ {
+ return (bool) preg_match('/^[\*,\/\-0-9A-Z]+$/', $value);
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/src/Cron/YearField.php b/core/vendor/mtdowling/cron-expression/src/Cron/YearField.php
new file mode 100644
index 0000000..db244fb
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/src/Cron/YearField.php
@@ -0,0 +1,37 @@
+isSatisfied($date->format('Y'), $value);
+ }
+
+ public function increment(DateTime $date, $invert = false)
+ {
+ if ($invert) {
+ $date->modify('-1 year');
+ $date->setDate($date->format('Y'), 12, 31);
+ $date->setTime(23, 59, 0);
+ } else {
+ $date->modify('+1 year');
+ $date->setDate($date->format('Y'), 1, 1);
+ $date->setTime(0, 0, 0);
+ }
+
+ return $this;
+ }
+
+ public function validate($value)
+ {
+ return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/AbstractFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/AbstractFieldTest.php
new file mode 100644
index 0000000..a1d653b
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/AbstractFieldTest.php
@@ -0,0 +1,86 @@
+
+ */
+class AbstractFieldTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\AbstractField::isRange
+ */
+ public function testTestsIfRange()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->isRange('1-2'));
+ $this->assertFalse($f->isRange('2'));
+ }
+
+ /**
+ * @covers Cron\AbstractField::isIncrementsOfRanges
+ */
+ public function testTestsIfIncrementsOfRanges()
+ {
+ $f = new DayOfWeekField();
+ $this->assertFalse($f->isIncrementsOfRanges('1-2'));
+ $this->assertTrue($f->isIncrementsOfRanges('1/2'));
+ $this->assertTrue($f->isIncrementsOfRanges('*/2'));
+ $this->assertTrue($f->isIncrementsOfRanges('3-12/2'));
+ }
+
+ /**
+ * @covers Cron\AbstractField::isInRange
+ */
+ public function testTestsIfInRange()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->isInRange('1', '1-2'));
+ $this->assertTrue($f->isInRange('2', '1-2'));
+ $this->assertTrue($f->isInRange('5', '4-12'));
+ $this->assertFalse($f->isInRange('3', '4-12'));
+ $this->assertFalse($f->isInRange('13', '4-12'));
+ }
+
+ /**
+ * @covers Cron\AbstractField::isInIncrementsOfRanges
+ */
+ public function testTestsIfInIncrementsOfRanges()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->isInIncrementsOfRanges('3', '3-59/2'));
+ $this->assertTrue($f->isInIncrementsOfRanges('13', '3-59/2'));
+ $this->assertTrue($f->isInIncrementsOfRanges('15', '3-59/2'));
+ $this->assertTrue($f->isInIncrementsOfRanges('14', '*/2'));
+ $this->assertFalse($f->isInIncrementsOfRanges('2', '3-59/13'));
+ $this->assertFalse($f->isInIncrementsOfRanges('14', '*/13'));
+ $this->assertFalse($f->isInIncrementsOfRanges('14', '3-59/2'));
+ $this->assertFalse($f->isInIncrementsOfRanges('3', '2-59'));
+ $this->assertFalse($f->isInIncrementsOfRanges('3', '2'));
+ $this->assertFalse($f->isInIncrementsOfRanges('3', '*'));
+ $this->assertFalse($f->isInIncrementsOfRanges('0', '*/0'));
+ $this->assertFalse($f->isInIncrementsOfRanges('1', '*/0'));
+
+ $this->assertTrue($f->isInIncrementsOfRanges('4', '4/10'));
+ $this->assertTrue($f->isInIncrementsOfRanges('14', '4/10'));
+ $this->assertTrue($f->isInIncrementsOfRanges('34', '4/10'));
+ }
+
+ /**
+ * @covers Cron\AbstractField::isSatisfied
+ */
+ public function testTestsIfSatisfied()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->isSatisfied('12', '3-13'));
+ $this->assertTrue($f->isSatisfied('15', '3-59/12'));
+ $this->assertTrue($f->isSatisfied('12', '*'));
+ $this->assertTrue($f->isSatisfied('12', '12'));
+ $this->assertFalse($f->isSatisfied('12', '3-11'));
+ $this->assertFalse($f->isSatisfied('12', '3-59/13'));
+ $this->assertFalse($f->isSatisfied('12', '11'));
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/CronExpressionTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/CronExpressionTest.php
new file mode 100644
index 0000000..f6fedb9
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/CronExpressionTest.php
@@ -0,0 +1,414 @@
+
+ */
+class CronExpressionTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\CronExpression::factory
+ */
+ public function testFactoryRecognizesTemplates()
+ {
+ $this->assertEquals('0 0 1 1 *', CronExpression::factory('@annually')->getExpression());
+ $this->assertEquals('0 0 1 1 *', CronExpression::factory('@yearly')->getExpression());
+ $this->assertEquals('0 0 * * 0', CronExpression::factory('@weekly')->getExpression());
+ }
+
+ /**
+ * @covers Cron\CronExpression::__construct
+ * @covers Cron\CronExpression::getExpression
+ * @covers Cron\CronExpression::__toString
+ */
+ public function testParsesCronSchedule()
+ {
+ // '2010-09-10 12:00:00'
+ $cron = CronExpression::factory('1 2-4 * 4,5,6 */3');
+ $this->assertEquals('1', $cron->getExpression(CronExpression::MINUTE));
+ $this->assertEquals('2-4', $cron->getExpression(CronExpression::HOUR));
+ $this->assertEquals('*', $cron->getExpression(CronExpression::DAY));
+ $this->assertEquals('4,5,6', $cron->getExpression(CronExpression::MONTH));
+ $this->assertEquals('*/3', $cron->getExpression(CronExpression::WEEKDAY));
+ $this->assertEquals('1 2-4 * 4,5,6 */3', $cron->getExpression());
+ $this->assertEquals('1 2-4 * 4,5,6 */3', (string) $cron);
+ $this->assertNull($cron->getExpression('foo'));
+
+ try {
+ $cron = CronExpression::factory('A 1 2 3 4');
+ $this->fail('Validation exception not thrown');
+ } catch (InvalidArgumentException $e) {
+ }
+ }
+
+ /**
+ * @covers Cron\CronExpression::__construct
+ * @covers Cron\CronExpression::getExpression
+ * @dataProvider scheduleWithDifferentSeparatorsProvider
+ */
+ public function testParsesCronScheduleWithAnySpaceCharsAsSeparators($schedule, array $expected)
+ {
+ $cron = CronExpression::factory($schedule);
+ $this->assertEquals($expected[0], $cron->getExpression(CronExpression::MINUTE));
+ $this->assertEquals($expected[1], $cron->getExpression(CronExpression::HOUR));
+ $this->assertEquals($expected[2], $cron->getExpression(CronExpression::DAY));
+ $this->assertEquals($expected[3], $cron->getExpression(CronExpression::MONTH));
+ $this->assertEquals($expected[4], $cron->getExpression(CronExpression::WEEKDAY));
+ $this->assertEquals($expected[5], $cron->getExpression(CronExpression::YEAR));
+ }
+
+ /**
+ * Data provider for testParsesCronScheduleWithAnySpaceCharsAsSeparators
+ *
+ * @return array
+ */
+ public static function scheduleWithDifferentSeparatorsProvider()
+ {
+ return array(
+ array("*\t*\t*\t*\t*\t*", array('*', '*', '*', '*', '*', '*')),
+ array("* * * * * *", array('*', '*', '*', '*', '*', '*')),
+ array("* \t * \t * \t * \t * \t *", array('*', '*', '*', '*', '*', '*')),
+ array("*\t \t*\t \t*\t \t*\t \t*\t \t*", array('*', '*', '*', '*', '*', '*')),
+ );
+ }
+
+ /**
+ * @covers Cron\CronExpression::__construct
+ * @covers Cron\CronExpression::setExpression
+ * @covers Cron\CronExpression::setPart
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidCronsWillFail()
+ {
+ // Only four values
+ $cron = CronExpression::factory('* * * 1');
+ }
+
+ /**
+ * @covers Cron\CronExpression::setPart
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidPartsWillFail()
+ {
+ // Only four values
+ $cron = CronExpression::factory('* * * * *');
+ $cron->setPart(1, 'abc');
+ }
+
+ /**
+ * Data provider for cron schedule
+ *
+ * @return array
+ */
+ public function scheduleProvider()
+ {
+ return array(
+ array('*/2 */2 * * *', '2015-08-10 21:47:27', '2015-08-10 22:00:00', false),
+ array('* * * * *', '2015-08-10 21:50:37', '2015-08-10 21:50:00', true),
+ array('* 20,21,22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true),
+ // Handles CSV values
+ array('* 20,22 * * *', '2015-08-10 21:50:00', '2015-08-10 22:00:00', false),
+ // CSV values can be complex
+ array('* 5,21-22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true),
+ array('7-9 * */9 * *', '2015-08-10 22:02:33', '2015-08-18 00:07:00', false),
+ // 15th minute, of the second hour, every 15 days, in January, every Friday
+ array('1 * * * 7', '2015-08-10 21:47:27', '2015-08-16 00:01:00', false),
+ // Test with exact times
+ array('47 21 * * *', strtotime('2015-08-10 21:47:30'), '2015-08-10 21:47:00', true),
+ // Test Day of the week (issue #1)
+ // According cron implementation, 0|7 = sunday, 1 => monday, etc
+ array('* * * * 0', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
+ array('* * * * 7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
+ array('* * * * 1', strtotime('2011-06-15 23:09:00'), '2011-06-20 00:00:00', false),
+ // Should return the sunday date as 7 equals 0
+ array('0 0 * * MON,SUN', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
+ array('0 0 * * 1,7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
+ array('0 0 * * 0-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
+ array('0 0 * * 7-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
+ array('0 0 * * 4-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
+ array('0 0 * * 7-3', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
+ array('0 0 * * 3-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
+ array('0 0 * * 3-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false),
+ // Test lists of values and ranges (Abhoryo)
+ array('0 0 * * 2-7', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false),
+ array('0 0 * * 0,2-6', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false),
+ array('0 0 * * 2-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false),
+ array('0 0 * * 4-7', strtotime('2011-07-19 00:00:00'), '2011-07-21 00:00:00', false),
+ // Test increments of ranges
+ array('0-12/4 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true),
+ array('4-59/2 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true),
+ array('4-59/2 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:06:00', true),
+ array('4-59/3 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:07:00', false),
+ //array('0 0 * * 0,2-6', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false),
+ // Test Day of the Week and the Day of the Month (issue #1)
+ array('0 0 1 1 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
+ array('0 0 1 JAN 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
+ array('0 0 1 * 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
+ array('0 0 L * *', strtotime('2011-07-15 00:00:00'), '2011-07-31 00:00:00', false),
+ // Test the W day of the week modifier for day of the month field
+ array('0 0 2W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
+ array('0 0 1W * *', strtotime('2011-05-01 00:00:00'), '2011-05-02 00:00:00', false),
+ array('0 0 1W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
+ array('0 0 3W * *', strtotime('2011-07-01 00:00:00'), '2011-07-04 00:00:00', false),
+ array('0 0 16W * *', strtotime('2011-07-01 00:00:00'), '2011-07-15 00:00:00', false),
+ array('0 0 28W * *', strtotime('2011-07-01 00:00:00'), '2011-07-28 00:00:00', false),
+ array('0 0 30W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
+ array('0 0 31W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
+ // Test the year field
+ array('* * * * * 2012', strtotime('2011-05-01 00:00:00'), '2012-01-01 00:00:00', false),
+ // Test the last weekday of a month
+ array('* * * * 5L', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
+ array('* * * * 6L', strtotime('2011-07-01 00:00:00'), '2011-07-30 00:00:00', false),
+ array('* * * * 7L', strtotime('2011-07-01 00:00:00'), '2011-07-31 00:00:00', false),
+ array('* * * * 1L', strtotime('2011-07-24 00:00:00'), '2011-07-25 00:00:00', false),
+ array('* * * * TUEL', strtotime('2011-07-24 00:00:00'), '2011-07-26 00:00:00', false),
+ array('* * * 1 5L', strtotime('2011-12-25 00:00:00'), '2012-01-27 00:00:00', false),
+ // Test the hash symbol for the nth weekday of a given month
+ array('* * * * 5#2', strtotime('2011-07-01 00:00:00'), '2011-07-08 00:00:00', false),
+ array('* * * * 5#1', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
+ array('* * * * 3#4', strtotime('2011-07-01 00:00:00'), '2011-07-27 00:00:00', false),
+ );
+ }
+
+ /**
+ * @covers Cron\CronExpression::isDue
+ * @covers Cron\CronExpression::getNextRunDate
+ * @covers Cron\DayOfMonthField
+ * @covers Cron\DayOfWeekField
+ * @covers Cron\MinutesField
+ * @covers Cron\HoursField
+ * @covers Cron\MonthField
+ * @covers Cron\YearField
+ * @covers Cron\CronExpression::getRunDate
+ * @dataProvider scheduleProvider
+ */
+ public function testDeterminesIfCronIsDue($schedule, $relativeTime, $nextRun, $isDue)
+ {
+ $relativeTimeString = is_int($relativeTime) ? date('Y-m-d H:i:s', $relativeTime) : $relativeTime;
+
+ // Test next run date
+ $cron = CronExpression::factory($schedule);
+ if (is_string($relativeTime)) {
+ $relativeTime = new DateTime($relativeTime);
+ } elseif (is_int($relativeTime)) {
+ $relativeTime = date('Y-m-d H:i:s', $relativeTime);
+ }
+ $this->assertEquals($isDue, $cron->isDue($relativeTime));
+ $next = $cron->getNextRunDate($relativeTime, 0, true);
+ $this->assertEquals(new DateTime($nextRun), $next);
+ }
+
+ /**
+ * @covers Cron\CronExpression::isDue
+ */
+ public function testIsDueHandlesDifferentDates()
+ {
+ $cron = CronExpression::factory('* * * * *');
+ $this->assertTrue($cron->isDue());
+ $this->assertTrue($cron->isDue('now'));
+ $this->assertTrue($cron->isDue(new DateTime('now')));
+ $this->assertTrue($cron->isDue(date('Y-m-d H:i')));
+ }
+
+ /**
+ * @covers Cron\CronExpression::isDue
+ */
+ public function testIsDueHandlesDifferentTimezones()
+ {
+ $cron = CronExpression::factory('0 15 * * 3'); //Wednesday at 15:00
+ $date = '2014-01-01 15:00'; //Wednesday
+ $utc = new DateTimeZone('UTC');
+ $amsterdam = new DateTimeZone('Europe/Amsterdam');
+ $tokyo = new DateTimeZone('Asia/Tokyo');
+
+ date_default_timezone_set('UTC');
+ $this->assertTrue($cron->isDue(new DateTime($date, $utc)));
+ $this->assertFalse($cron->isDue(new DateTime($date, $amsterdam)));
+ $this->assertFalse($cron->isDue(new DateTime($date, $tokyo)));
+
+ date_default_timezone_set('Europe/Amsterdam');
+ $this->assertFalse($cron->isDue(new DateTime($date, $utc)));
+ $this->assertTrue($cron->isDue(new DateTime($date, $amsterdam)));
+ $this->assertFalse($cron->isDue(new DateTime($date, $tokyo)));
+
+ date_default_timezone_set('Asia/Tokyo');
+ $this->assertFalse($cron->isDue(new DateTime($date, $utc)));
+ $this->assertFalse($cron->isDue(new DateTime($date, $amsterdam)));
+ $this->assertTrue($cron->isDue(new DateTime($date, $tokyo)));
+ }
+
+ /**
+ * @covers Cron\CronExpression::getPreviousRunDate
+ */
+ public function testCanGetPreviousRunDates()
+ {
+ $cron = CronExpression::factory('* * * * *');
+ $next = $cron->getNextRunDate('now');
+ $two = $cron->getNextRunDate('now', 1);
+ $this->assertEquals($next, $cron->getPreviousRunDate($two));
+
+ $cron = CronExpression::factory('* */2 * * *');
+ $next = $cron->getNextRunDate('now');
+ $two = $cron->getNextRunDate('now', 1);
+ $this->assertEquals($next, $cron->getPreviousRunDate($two));
+
+ $cron = CronExpression::factory('* * * */2 *');
+ $next = $cron->getNextRunDate('now');
+ $two = $cron->getNextRunDate('now', 1);
+ $this->assertEquals($next, $cron->getPreviousRunDate($two));
+ }
+
+ /**
+ * @covers Cron\CronExpression::getMultipleRunDates
+ */
+ public function testProvidesMultipleRunDates()
+ {
+ $cron = CronExpression::factory('*/2 * * * *');
+ $this->assertEquals(array(
+ new DateTime('2008-11-09 00:00:00'),
+ new DateTime('2008-11-09 00:02:00'),
+ new DateTime('2008-11-09 00:04:00'),
+ new DateTime('2008-11-09 00:06:00')
+ ), $cron->getMultipleRunDates(4, '2008-11-09 00:00:00', false, true));
+ }
+
+ /**
+ * @covers Cron\CronExpression::getMultipleRunDates
+ * @covers Cron\CronExpression::setMaxIterationCount
+ */
+ public function testProvidesMultipleRunDatesForTheFarFuture() {
+ // Fails with the default 1000 iteration limit
+ $cron = CronExpression::factory('0 0 12 1 * */2');
+ $cron->setMaxIterationCount(2000);
+ $this->assertEquals(array(
+ new DateTime('2016-01-12 00:00:00'),
+ new DateTime('2018-01-12 00:00:00'),
+ new DateTime('2020-01-12 00:00:00'),
+ new DateTime('2022-01-12 00:00:00'),
+ new DateTime('2024-01-12 00:00:00'),
+ new DateTime('2026-01-12 00:00:00'),
+ new DateTime('2028-01-12 00:00:00'),
+ new DateTime('2030-01-12 00:00:00'),
+ new DateTime('2032-01-12 00:00:00'),
+ ), $cron->getMultipleRunDates(9, '2015-04-28 00:00:00', false, true));
+ }
+
+ /**
+ * @covers Cron\CronExpression
+ */
+ public function testCanIterateOverNextRuns()
+ {
+ $cron = CronExpression::factory('@weekly');
+ $nextRun = $cron->getNextRunDate("2008-11-09 08:00:00");
+ $this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00"));
+
+ // true is cast to 1
+ $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", true, true);
+ $this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00"));
+
+ // You can iterate over them
+ $nextRun = $cron->getNextRunDate($cron->getNextRunDate("2008-11-09 00:00:00", 1, true), 1, true);
+ $this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00"));
+
+ // You can skip more than one
+ $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 2, true);
+ $this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00"));
+ $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 3, true);
+ $this->assertEquals($nextRun, new DateTime("2008-11-30 00:00:00"));
+ }
+
+ /**
+ * @covers Cron\CronExpression::getRunDate
+ */
+ public function testSkipsCurrentDateByDefault()
+ {
+ $cron = CronExpression::factory('* * * * *');
+ $current = new DateTime('now');
+ $next = $cron->getNextRunDate($current);
+ $nextPrev = $cron->getPreviousRunDate($next);
+ $this->assertEquals($current->format('Y-m-d H:i:00'), $nextPrev->format('Y-m-d H:i:s'));
+ }
+
+ /**
+ * @covers Cron\CronExpression::getRunDate
+ * @ticket 7
+ */
+ public function testStripsForSeconds()
+ {
+ $cron = CronExpression::factory('* * * * *');
+ $current = new DateTime('2011-09-27 10:10:54');
+ $this->assertEquals('2011-09-27 10:11:00', $cron->getNextRunDate($current)->format('Y-m-d H:i:s'));
+ }
+
+ /**
+ * @covers Cron\CronExpression::getRunDate
+ */
+ public function testFixesPhpBugInDateIntervalMonth()
+ {
+ $cron = CronExpression::factory('0 0 27 JAN *');
+ $this->assertEquals('2011-01-27 00:00:00', $cron->getPreviousRunDate('2011-08-22 00:00:00')->format('Y-m-d H:i:s'));
+ }
+
+ public function testIssue29()
+ {
+ $cron = CronExpression::factory('@weekly');
+ $this->assertEquals(
+ '2013-03-10 00:00:00',
+ $cron->getPreviousRunDate('2013-03-17 00:00:00')->format('Y-m-d H:i:s')
+ );
+ }
+
+ /**
+ * @see https://github.com/mtdowling/cron-expression/issues/20
+ */
+ public function testIssue20() {
+ $e = CronExpression::factory('* * * * MON#1');
+ $this->assertTrue($e->isDue(new DateTime('2014-04-07 00:00:00')));
+ $this->assertFalse($e->isDue(new DateTime('2014-04-14 00:00:00')));
+ $this->assertFalse($e->isDue(new DateTime('2014-04-21 00:00:00')));
+
+ $e = CronExpression::factory('* * * * SAT#2');
+ $this->assertFalse($e->isDue(new DateTime('2014-04-05 00:00:00')));
+ $this->assertTrue($e->isDue(new DateTime('2014-04-12 00:00:00')));
+ $this->assertFalse($e->isDue(new DateTime('2014-04-19 00:00:00')));
+
+ $e = CronExpression::factory('* * * * SUN#3');
+ $this->assertFalse($e->isDue(new DateTime('2014-04-13 00:00:00')));
+ $this->assertTrue($e->isDue(new DateTime('2014-04-20 00:00:00')));
+ $this->assertFalse($e->isDue(new DateTime('2014-04-27 00:00:00')));
+ }
+
+ /**
+ * @covers Cron\CronExpression::getRunDate
+ */
+ public function testKeepOriginalTime()
+ {
+ $now = new \DateTime;
+ $strNow = $now->format(DateTime::ISO8601);
+ $cron = CronExpression::factory('0 0 * * *');
+ $cron->getPreviousRunDate($now);
+ $this->assertEquals($strNow, $now->format(DateTime::ISO8601));
+ }
+
+ /**
+ * @covers Cron\CronExpression::__construct
+ * @covers Cron\CronExpression::factory
+ * @covers Cron\CronExpression::isValidExpression
+ * @covers Cron\CronExpression::setExpression
+ * @covers Cron\CronExpression::setPart
+ */
+ public function testValidationWorks()
+ {
+ // Invalid. Only four values
+ $this->assertFalse(CronExpression::isValidExpression('* * * 1'));
+ // Valid
+ $this->assertTrue(CronExpression::isValidExpression('* * * * 1'));
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfMonthFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfMonthFieldTest.php
new file mode 100644
index 0000000..eff0455
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfMonthFieldTest.php
@@ -0,0 +1,61 @@
+
+ */
+class DayOfMonthFieldTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\DayOfMonthField::validate
+ */
+ public function testValidatesField()
+ {
+ $f = new DayOfMonthField();
+ $this->assertTrue($f->validate('1'));
+ $this->assertTrue($f->validate('*'));
+ $this->assertTrue($f->validate('5W,L'));
+ $this->assertFalse($f->validate('1.'));
+ }
+
+ /**
+ * @covers Cron\DayOfMonthField::isSatisfiedBy
+ */
+ public function testChecksIfSatisfied()
+ {
+ $f = new DayOfMonthField();
+ $this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
+ }
+
+ /**
+ * @covers Cron\DayOfMonthField::increment
+ */
+ public function testIncrementsDate()
+ {
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f = new DayOfMonthField();
+ $f->increment($d);
+ $this->assertEquals('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s'));
+
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f->increment($d, true);
+ $this->assertEquals('2011-03-14 23:59:00', $d->format('Y-m-d H:i:s'));
+ }
+
+ /**
+ * Day of the month cannot accept a 0 value, it must be between 1 and 31
+ * See Github issue #120
+ *
+ * @since 2017-01-22
+ */
+ public function testDoesNotAccept0Date()
+ {
+ $f = new DayOfMonthField();
+ $this->assertFalse($f->validate(0));
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfWeekFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfWeekFieldTest.php
new file mode 100644
index 0000000..182d5e9
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/DayOfWeekFieldTest.php
@@ -0,0 +1,117 @@
+
+ */
+class DayOfWeekFieldTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\DayOfWeekField::validate
+ */
+ public function testValidatesField()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->validate('1'));
+ $this->assertTrue($f->validate('*'));
+ $this->assertTrue($f->validate('*/3,1,1-12'));
+ $this->assertTrue($f->validate('SUN-2'));
+ $this->assertFalse($f->validate('1.'));
+ }
+
+ /**
+ * @covers Cron\DayOfWeekField::isSatisfiedBy
+ */
+ public function testChecksIfSatisfied()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
+ }
+
+ /**
+ * @covers Cron\DayOfWeekField::increment
+ */
+ public function testIncrementsDate()
+ {
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f = new DayOfWeekField();
+ $f->increment($d);
+ $this->assertEquals('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s'));
+
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f->increment($d, true);
+ $this->assertEquals('2011-03-14 23:59:00', $d->format('Y-m-d H:i:s'));
+ }
+
+ /**
+ * @covers Cron\DayOfWeekField::isSatisfiedBy
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Weekday must be a value between 0 and 7. 12 given
+ */
+ public function testValidatesHashValueWeekday()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->isSatisfiedBy(new DateTime(), '12#1'));
+ }
+
+ /**
+ * @covers Cron\DayOfWeekField::isSatisfiedBy
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage There are never more than 5 of a given weekday in a month
+ */
+ public function testValidatesHashValueNth()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->isSatisfiedBy(new DateTime(), '3#6'));
+ }
+
+ /**
+ * @covers Cron\DayOfWeekField::validate
+ */
+ public function testValidateWeekendHash()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->validate('MON#1'));
+ $this->assertTrue($f->validate('TUE#2'));
+ $this->assertTrue($f->validate('WED#3'));
+ $this->assertTrue($f->validate('THU#4'));
+ $this->assertTrue($f->validate('FRI#5'));
+ $this->assertTrue($f->validate('SAT#1'));
+ $this->assertTrue($f->validate('SUN#3'));
+ $this->assertTrue($f->validate('MON#1,MON#3'));
+ }
+
+ /**
+ * @covers Cron\DayOfWeekField::isSatisfiedBy
+ */
+ public function testHandlesZeroAndSevenDayOfTheWeekValues()
+ {
+ $f = new DayOfWeekField();
+ $this->assertTrue($f->isSatisfiedBy(new DateTime('2011-09-04 00:00:00'), '0-2'));
+ $this->assertTrue($f->isSatisfiedBy(new DateTime('2011-09-04 00:00:00'), '6-0'));
+
+ $this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), 'SUN'));
+ $this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), 'SUN#3'));
+ $this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), '0#3'));
+ $this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), '7#3'));
+ }
+
+ /**
+ * @see https://github.com/mtdowling/cron-expression/issues/47
+ */
+ public function testIssue47() {
+ $f = new DayOfWeekField();
+ $this->assertFalse($f->validate('mon,'));
+ $this->assertFalse($f->validate('mon-'));
+ $this->assertFalse($f->validate('*/2,'));
+ $this->assertFalse($f->validate('-mon'));
+ $this->assertFalse($f->validate(',1'));
+ $this->assertFalse($f->validate('*-'));
+ $this->assertFalse($f->validate(',-'));
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/FieldFactoryTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/FieldFactoryTest.php
new file mode 100644
index 0000000..f34cc9b
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/FieldFactoryTest.php
@@ -0,0 +1,43 @@
+
+ */
+class FieldFactoryTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\FieldFactory::getField
+ */
+ public function testRetrievesFieldInstances()
+ {
+ $mappings = array(
+ 0 => 'Cron\MinutesField',
+ 1 => 'Cron\HoursField',
+ 2 => 'Cron\DayOfMonthField',
+ 3 => 'Cron\MonthField',
+ 4 => 'Cron\DayOfWeekField',
+ 5 => 'Cron\YearField'
+ );
+
+ $f = new FieldFactory();
+
+ foreach ($mappings as $position => $class) {
+ $this->assertEquals($class, get_class($f->getField($position)));
+ }
+ }
+
+ /**
+ * @covers Cron\FieldFactory::getField
+ * @expectedException InvalidArgumentException
+ */
+ public function testValidatesFieldPosition()
+ {
+ $f = new FieldFactory();
+ $f->getField(-1);
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/HoursFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/HoursFieldTest.php
new file mode 100644
index 0000000..d2d8a22
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/HoursFieldTest.php
@@ -0,0 +1,75 @@
+
+ */
+class HoursFieldTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\HoursField::validate
+ */
+ public function testValidatesField()
+ {
+ $f = new HoursField();
+ $this->assertTrue($f->validate('1'));
+ $this->assertTrue($f->validate('*'));
+ $this->assertTrue($f->validate('*/3,1,1-12'));
+ }
+
+ /**
+ * @covers Cron\HoursField::increment
+ */
+ public function testIncrementsDate()
+ {
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f = new HoursField();
+ $f->increment($d);
+ $this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
+
+ $d->setTime(11, 15, 0);
+ $f->increment($d, true);
+ $this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
+ }
+
+ /**
+ * @covers Cron\HoursField::increment
+ */
+ public function testIncrementsDateWithThirtyMinuteOffsetTimezone()
+ {
+ $tz = date_default_timezone_get();
+ date_default_timezone_set('America/St_Johns');
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f = new HoursField();
+ $f->increment($d);
+ $this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
+
+ $d->setTime(11, 15, 0);
+ $f->increment($d, true);
+ $this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
+ date_default_timezone_set($tz);
+ }
+
+ /**
+ * @covers Cron\HoursField::increment
+ */
+ public function testIncrementDateWithFifteenMinuteOffsetTimezone()
+ {
+ $tz = date_default_timezone_get();
+ date_default_timezone_set('Asia/Kathmandu');
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f = new HoursField();
+ $f->increment($d);
+ $this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
+
+ $d->setTime(11, 15, 0);
+ $f->increment($d, true);
+ $this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
+ date_default_timezone_set($tz);
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/MinutesFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/MinutesFieldTest.php
new file mode 100644
index 0000000..af3fef7
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/MinutesFieldTest.php
@@ -0,0 +1,37 @@
+
+ */
+class MinutesFieldTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\MinutesField::validate
+ */
+ public function testValidatesField()
+ {
+ $f = new MinutesField();
+ $this->assertTrue($f->validate('1'));
+ $this->assertTrue($f->validate('*'));
+ $this->assertTrue($f->validate('*/3,1,1-12'));
+ }
+
+ /**
+ * @covers Cron\MinutesField::increment
+ */
+ public function testIncrementsDate()
+ {
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f = new MinutesField();
+ $f->increment($d);
+ $this->assertEquals('2011-03-15 11:16:00', $d->format('Y-m-d H:i:s'));
+ $f->increment($d, true);
+ $this->assertEquals('2011-03-15 11:15:00', $d->format('Y-m-d H:i:s'));
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/MonthFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/MonthFieldTest.php
new file mode 100644
index 0000000..2d9b0ad
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/MonthFieldTest.php
@@ -0,0 +1,81 @@
+
+ */
+class MonthFieldTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\MonthField::validate
+ */
+ public function testValidatesField()
+ {
+ $f = new MonthField();
+ $this->assertTrue($f->validate('12'));
+ $this->assertTrue($f->validate('*'));
+ $this->assertTrue($f->validate('*/10,2,1-12'));
+ $this->assertFalse($f->validate('1.fix-regexp'));
+ }
+
+ /**
+ * @covers Cron\MonthField::increment
+ */
+ public function testIncrementsDate()
+ {
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f = new MonthField();
+ $f->increment($d);
+ $this->assertEquals('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s'));
+
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f->increment($d, true);
+ $this->assertEquals('2011-02-28 23:59:00', $d->format('Y-m-d H:i:s'));
+ }
+
+ /**
+ * @covers Cron\MonthField::increment
+ */
+ public function testIncrementsDateWithThirtyMinuteTimezone()
+ {
+ $tz = date_default_timezone_get();
+ date_default_timezone_set('America/St_Johns');
+ $d = new DateTime('2011-03-31 11:59:59');
+ $f = new MonthField();
+ $f->increment($d);
+ $this->assertEquals('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s'));
+
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f->increment($d, true);
+ $this->assertEquals('2011-02-28 23:59:00', $d->format('Y-m-d H:i:s'));
+ date_default_timezone_set($tz);
+ }
+
+
+ /**
+ * @covers Cron\MonthField::increment
+ */
+ public function testIncrementsYearAsNeeded()
+ {
+ $f = new MonthField();
+ $d = new DateTime('2011-12-15 00:00:00');
+ $f->increment($d);
+ $this->assertEquals('2012-01-01 00:00:00', $d->format('Y-m-d H:i:s'));
+ }
+
+ /**
+ * @covers Cron\MonthField::increment
+ */
+ public function testDecrementsYearAsNeeded()
+ {
+ $f = new MonthField();
+ $d = new DateTime('2011-01-15 00:00:00');
+ $f->increment($d, true);
+ $this->assertEquals('2010-12-31 23:59:00', $d->format('Y-m-d H:i:s'));
+ }
+}
diff --git a/core/vendor/mtdowling/cron-expression/tests/Cron/YearFieldTest.php b/core/vendor/mtdowling/cron-expression/tests/Cron/YearFieldTest.php
new file mode 100644
index 0000000..b5059ec
--- /dev/null
+++ b/core/vendor/mtdowling/cron-expression/tests/Cron/YearFieldTest.php
@@ -0,0 +1,37 @@
+
+ */
+class YearFieldTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Cron\YearField::validate
+ */
+ public function testValidatesField()
+ {
+ $f = new YearField();
+ $this->assertTrue($f->validate('2011'));
+ $this->assertTrue($f->validate('*'));
+ $this->assertTrue($f->validate('*/10,2012,1-12'));
+ }
+
+ /**
+ * @covers Cron\YearField::increment
+ */
+ public function testIncrementsDate()
+ {
+ $d = new DateTime('2011-03-15 11:15:00');
+ $f = new YearField();
+ $f->increment($d);
+ $this->assertEquals('2012-01-01 00:00:00', $d->format('Y-m-d H:i:s'));
+ $f->increment($d, true);
+ $this->assertEquals('2011-12-31 23:59:00', $d->format('Y-m-d H:i:s'));
+ }
+}
diff --git a/core/vendor/nesbot/carbon/LICENSE b/core/vendor/nesbot/carbon/LICENSE
new file mode 100644
index 0000000..6de45eb
--- /dev/null
+++ b/core/vendor/nesbot/carbon/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) Brian Nesbitt
+
+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/core/vendor/nesbot/carbon/bin/upgrade-carbon b/core/vendor/nesbot/carbon/bin/upgrade-carbon
new file mode 100644
index 0000000..49c4c9a
--- /dev/null
+++ b/core/vendor/nesbot/carbon/bin/upgrade-carbon
@@ -0,0 +1,34 @@
+#!/usr/bin/env php
+=5.3.9",
+ "kylekatarnls/update-helper": "^1.1",
+ "symfony/translation": "~2.6 || ~3.0 || ~4.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.2",
+ "friendsofphp/php-cs-fixer": "~2",
+ "phpunit/phpunit": "^4.8.35 || ^5.7"
+ },
+ "autoload": {
+ "psr-4": {
+ "": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests/"
+ }
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "scripts": {
+ "test": [
+ "@phpunit",
+ "@phpcs"
+ ],
+ "phpunit": "phpunit --verbose --coverage-clover=coverage.xml",
+ "phpcs": "php-cs-fixer fix -v --diff --dry-run",
+ "phpstan": "phpstan analyse --configuration phpstan.neon --level 3 src tests",
+ "post-autoload-dump": [
+ "UpdateHelper\\UpdateHelper::check"
+ ],
+ "upgrade-carbon": [
+ "Carbon\\Upgrade::upgrade"
+ ]
+ },
+ "extra": {
+ "update-helper": "Carbon\\Upgrade",
+ "laravel": {
+ "providers": [
+ "Carbon\\Laravel\\ServiceProvider"
+ ]
+ }
+ }
+}
diff --git a/core/vendor/nesbot/carbon/readme.md b/core/vendor/nesbot/carbon/readme.md
new file mode 100644
index 0000000..5e9d1cc
--- /dev/null
+++ b/core/vendor/nesbot/carbon/readme.md
@@ -0,0 +1,94 @@
+# Carbon
+
+[![Latest Stable Version](https://poser.pugx.org/nesbot/carbon/v/stable.png)](https://packagist.org/packages/nesbot/carbon)
+[![Total Downloads](https://poser.pugx.org/nesbot/carbon/downloads.png)](https://packagist.org/packages/nesbot/carbon)
+[![Build Status](https://travis-ci.org/briannesbitt/Carbon.svg?branch=master)](https://travis-ci.org/briannesbitt/Carbon)
+[![StyleCI](https://styleci.io/repos/5724990/shield?style=flat)](https://styleci.io/repos/5724990)
+[![codecov.io](https://codecov.io/github/briannesbitt/Carbon/coverage.svg?branch=master)](https://codecov.io/github/briannesbitt/Carbon?branch=master)
+[![PHP-Eye](https://php-eye.com/badge/nesbot/carbon/tested.svg?style=flat)](https://php-eye.com/package/nesbot/carbon)
+[![PHPStan](https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat)](https://github.com/phpstan/phpstan)
+
+A simple PHP API extension for DateTime. [http://carbon.nesbot.com](http://carbon.nesbot.com)
+
+```php
+use Carbon\Carbon;
+
+printf("Right now is %s", Carbon::now()->toDateTimeString());
+printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver')); //implicit __toString()
+$tomorrow = Carbon::now()->addDay();
+$lastWeek = Carbon::now()->subWeek();
+$nextSummerOlympics = Carbon::createFromDate(2016)->addYears(4);
+
+$officialDate = Carbon::now()->toRfc2822String();
+
+$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age;
+
+$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London');
+
+$internetWillBlowUpOn = Carbon::create(2038, 01, 19, 3, 14, 7, 'GMT');
+
+// Don't really want this to happen so mock now
+Carbon::setTestNow(Carbon::createFromDate(2000, 1, 1));
+
+// comparisons are always done in UTC
+if (Carbon::now()->gte($internetWillBlowUpOn)) {
+ die();
+}
+
+// Phew! Return to normal behaviour
+Carbon::setTestNow();
+
+if (Carbon::now()->isWeekend()) {
+ echo 'Party!';
+}
+echo Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago'
+
+// ... but also does 'from now', 'after' and 'before'
+// rolling up to seconds, minutes, hours, days, months, years
+
+$daysSinceEpoch = Carbon::createFromTimestamp(0)->diffInDays();
+```
+
+## Installation
+
+### With Composer
+
+```
+$ composer require nesbot/carbon
+```
+
+```json
+{
+ "require": {
+ "nesbot/carbon": "~1.21"
+ }
+}
+```
+
+```php
+
+
+### Without Composer
+
+Why are you not using [composer](http://getcomposer.org/)? Download [Carbon.php](https://github.com/briannesbitt/Carbon/blob/master/src/Carbon/Carbon.php) from the repo and save the file into your project path somewhere.
+
+```php
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Carbon;
+
+use Carbon\Exceptions\InvalidDateException;
+use Closure;
+use DateInterval;
+use DatePeriod;
+use DateTime;
+use DateTimeInterface;
+use DateTimeZone;
+use InvalidArgumentException;
+use JsonSerializable;
+use Symfony\Component\Translation\TranslatorInterface;
+
+/**
+ * A simple API extension for DateTime
+ *
+ * @property int $year
+ * @property int $yearIso
+ * @property int $month
+ * @property int $day
+ * @property int $hour
+ * @property int $minute
+ * @property int $second
+ * @property int $timestamp seconds since the Unix Epoch
+ * @property \DateTimeZone $timezone the current timezone
+ * @property \DateTimeZone $tz alias of timezone
+ * @property-read int $micro
+ * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday)
+ * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday)
+ * @property-read int $dayOfYear 0 through 365
+ * @property-read int $weekOfMonth 1 through 5
+ * @property-read int $weekNumberInMonth 1 through 5
+ * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday
+ * @property-read int $daysInMonth number of days in the given month
+ * @property-read int $age does a diffInYears() with default parameters
+ * @property-read int $quarter the quarter of this instance, 1 - 4
+ * @property-read int $offset the timezone offset in seconds from UTC
+ * @property-read int $offsetHours the timezone offset in hours from UTC
+ * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise
+ * @property-read bool $local checks if the timezone is local, true if local, false otherwise
+ * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise
+ * @property-read string $timezoneName
+ * @property-read string $tzName
+ * @property-read string $englishDayOfWeek the day of week in English
+ * @property-read string $shortEnglishDayOfWeek the abbreviated day of week in English
+ * @property-read string $englishMonth the day of week in English
+ * @property-read string $shortEnglishMonth the abbreviated day of week in English
+ * @property-read string $localeDayOfWeek the day of week in current locale LC_TIME
+ * @property-read string $shortLocaleDayOfWeek the abbreviated day of week in current locale LC_TIME
+ * @property-read string $localeMonth the month in current locale LC_TIME
+ * @property-read string $shortLocaleMonth the abbreviated month in current locale LC_TIME
+ */
+class Carbon extends DateTime implements JsonSerializable
+{
+ const NO_ZERO_DIFF = 01;
+ const JUST_NOW = 02;
+ const ONE_DAY_WORDS = 04;
+ const TWO_DAY_WORDS = 010;
+
+ // Substitutes for Carbon 2 modes
+ const DIFF_RELATIVE_TO_NOW = 'relative-to-now';
+ const DIFF_RELATIVE_TO_OTHER = 'relative-to-other';
+
+ /**
+ * The day constants.
+ */
+ const SUNDAY = 0;
+ const MONDAY = 1;
+ const TUESDAY = 2;
+ const WEDNESDAY = 3;
+ const THURSDAY = 4;
+ const FRIDAY = 5;
+ const SATURDAY = 6;
+
+ /**
+ * Names of days of the week.
+ *
+ * @var array
+ */
+ protected static $days = array(
+ self::SUNDAY => 'Sunday',
+ self::MONDAY => 'Monday',
+ self::TUESDAY => 'Tuesday',
+ self::WEDNESDAY => 'Wednesday',
+ self::THURSDAY => 'Thursday',
+ self::FRIDAY => 'Friday',
+ self::SATURDAY => 'Saturday',
+ );
+
+ /**
+ * Number of X in Y.
+ */
+ const YEARS_PER_MILLENNIUM = 1000;
+ const YEARS_PER_CENTURY = 100;
+ const YEARS_PER_DECADE = 10;
+ const MONTHS_PER_YEAR = 12;
+ const MONTHS_PER_QUARTER = 3;
+ const WEEKS_PER_YEAR = 52;
+ const WEEKS_PER_MONTH = 4;
+ const DAYS_PER_WEEK = 7;
+ const HOURS_PER_DAY = 24;
+ const MINUTES_PER_HOUR = 60;
+ const SECONDS_PER_MINUTE = 60;
+ const MICROSECONDS_PER_MILLISECOND = 1000;
+ const MICROSECONDS_PER_SECOND = 1000000;
+
+ /**
+ * RFC7231 DateTime format.
+ *
+ * @var string
+ */
+ const RFC7231_FORMAT = 'D, d M Y H:i:s \G\M\T';
+
+ /**
+ * Default format to use for __toString method when type juggling occurs.
+ *
+ * @var string
+ */
+ const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s';
+
+ /**
+ * Format for converting mocked time, includes microseconds.
+ *
+ * @var string
+ */
+ const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u';
+
+ /**
+ * Customizable PHP_INT_SIZE override.
+ *
+ * @var int
+ */
+ public static $PHPIntSize = PHP_INT_SIZE;
+
+ /**
+ * Format to use for __toString method when type juggling occurs.
+ *
+ * @var string
+ */
+ protected static $toStringFormat = self::DEFAULT_TO_STRING_FORMAT;
+
+ /**
+ * First day of week.
+ *
+ * @var int
+ */
+ protected static $weekStartsAt = self::MONDAY;
+
+ /**
+ * Last day of week.
+ *
+ * @var int
+ */
+ protected static $weekEndsAt = self::SUNDAY;
+
+ /**
+ * Days of weekend.
+ *
+ * @var array
+ */
+ protected static $weekendDays = array(
+ self::SATURDAY,
+ self::SUNDAY,
+ );
+
+ /**
+ * Midday/noon hour.
+ *
+ * @var int
+ */
+ protected static $midDayAt = 12;
+
+ /**
+ * Format regex patterns.
+ *
+ * @var array
+ */
+ protected static $regexFormats = array(
+ 'd' => '(3[01]|[12][0-9]|0[1-9])',
+ 'D' => '([a-zA-Z]{3})',
+ 'j' => '([123][0-9]|[1-9])',
+ 'l' => '([a-zA-Z]{2,})',
+ 'N' => '([1-7])',
+ 'S' => '([a-zA-Z]{2})',
+ 'w' => '([0-6])',
+ 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])',
+ 'W' => '(5[012]|[1-4][0-9]|[1-9])',
+ 'F' => '([a-zA-Z]{2,})',
+ 'm' => '(1[012]|0[1-9])',
+ 'M' => '([a-zA-Z]{3})',
+ 'n' => '(1[012]|[1-9])',
+ 't' => '(2[89]|3[01])',
+ 'L' => '(0|1)',
+ 'o' => '([1-9][0-9]{0,4})',
+ 'Y' => '([1-9]?[0-9]{4})',
+ 'y' => '([0-9]{2})',
+ 'a' => '(am|pm)',
+ 'A' => '(AM|PM)',
+ 'B' => '([0-9]{3})',
+ 'g' => '(1[012]|[1-9])',
+ 'G' => '(2[0-3]|1?[0-9])',
+ 'h' => '(1[012]|0[1-9])',
+ 'H' => '(2[0-3]|[01][0-9])',
+ 'i' => '([0-5][0-9])',
+ 's' => '([0-5][0-9])',
+ 'u' => '([0-9]{1,6})',
+ 'v' => '([0-9]{1,3})',
+ 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\/[a-zA-Z]*)',
+ 'I' => '(0|1)',
+ 'O' => '([\+\-](1[012]|0[0-9])[0134][05])',
+ 'P' => '([\+\-](1[012]|0[0-9]):[0134][05])',
+ 'T' => '([a-zA-Z]{1,5})',
+ 'Z' => '(-?[1-5]?[0-9]{1,4})',
+ 'U' => '([0-9]*)',
+
+ // The formats below are combinations of the above formats.
+ '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
+ );
+
+ /**
+ * A test Carbon instance to be returned when now instances are created.
+ *
+ * @var \Carbon\Carbon
+ */
+ protected static $testNow;
+
+ /**
+ * A translator to ... er ... translate stuff.
+ *
+ * @var \Symfony\Component\Translation\TranslatorInterface
+ */
+ protected static $translator;
+
+ /**
+ * The errors that can occur.
+ *
+ * @var array
+ */
+ protected static $lastErrors;
+
+ /**
+ * The custom Carbon JSON serializer.
+ *
+ * @var callable|null
+ */
+ protected static $serializer;
+
+ /**
+ * The registered string macros.
+ *
+ * @var array
+ */
+ protected static $localMacros = array();
+
+ /**
+ * Will UTF8 encoding be used to print localized date/time ?
+ *
+ * @var bool
+ */
+ protected static $utf8 = false;
+
+ /**
+ * Add microseconds to now on PHP < 7.1 and 7.1.3. true by default.
+ *
+ * @var bool
+ */
+ protected static $microsecondsFallback = true;
+
+ /**
+ * Indicates if months should be calculated with overflow.
+ *
+ * @var bool
+ */
+ protected static $monthsOverflow = true;
+
+ /**
+ * Indicates if years should be calculated with overflow.
+ *
+ * @var bool
+ */
+ protected static $yearsOverflow = true;
+
+ /**
+ * Indicates if years are compared with month by default so isSameMonth and isSameQuarter have $ofSameYear set
+ * to true by default.
+ *
+ * @var bool
+ */
+ protected static $compareYearWithMonth = false;
+
+ /**
+ * Options for diffForHumans().
+ *
+ * @var int
+ */
+ protected static $humanDiffOptions = self::NO_ZERO_DIFF;
+
+ /**
+ * @param int $humanDiffOptions
+ */
+ public static function setHumanDiffOptions($humanDiffOptions)
+ {
+ static::$humanDiffOptions = $humanDiffOptions;
+ }
+
+ /**
+ * @param int $humanDiffOption
+ */
+ public static function enableHumanDiffOption($humanDiffOption)
+ {
+ static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption;
+ }
+
+ /**
+ * @param int $humanDiffOption
+ */
+ public static function disableHumanDiffOption($humanDiffOption)
+ {
+ static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption;
+ }
+
+ /**
+ * @return int
+ */
+ public static function getHumanDiffOptions()
+ {
+ return static::$humanDiffOptions;
+ }
+
+ /**
+ * Add microseconds to now on PHP < 7.1 and 7.1.3 if set to true,
+ * let microseconds to 0 on those PHP versions if false.
+ *
+ * @param bool $microsecondsFallback
+ */
+ public static function useMicrosecondsFallback($microsecondsFallback = true)
+ {
+ static::$microsecondsFallback = $microsecondsFallback;
+ }
+
+ /**
+ * Return true if microseconds fallback on PHP < 7.1 and 7.1.3 is
+ * enabled. false if disabled.
+ *
+ * @return bool
+ */
+ public static function isMicrosecondsFallbackEnabled()
+ {
+ return static::$microsecondsFallback;
+ }
+
+ /**
+ * Indicates if months should be calculated with overflow.
+ *
+ * @param bool $monthsOverflow
+ *
+ * @return void
+ */
+ public static function useMonthsOverflow($monthsOverflow = true)
+ {
+ static::$monthsOverflow = $monthsOverflow;
+ }
+
+ /**
+ * Reset the month overflow behavior.
+ *
+ * @return void
+ */
+ public static function resetMonthsOverflow()
+ {
+ static::$monthsOverflow = true;
+ }
+
+ /**
+ * Get the month overflow behavior.
+ *
+ * @return bool
+ */
+ public static function shouldOverflowMonths()
+ {
+ return static::$monthsOverflow;
+ }
+
+ /**
+ * Indicates if years should be calculated with overflow.
+ *
+ * @param bool $yearsOverflow
+ *
+ * @return void
+ */
+ public static function useYearsOverflow($yearsOverflow = true)
+ {
+ static::$yearsOverflow = $yearsOverflow;
+ }
+
+ /**
+ * Reset the month overflow behavior.
+ *
+ * @return void
+ */
+ public static function resetYearsOverflow()
+ {
+ static::$yearsOverflow = true;
+ }
+
+ /**
+ * Get the month overflow behavior.
+ *
+ * @return bool
+ */
+ public static function shouldOverflowYears()
+ {
+ return static::$yearsOverflow;
+ }
+
+ /**
+ * Get the month comparison default behavior.
+ *
+ * @return bool
+ */
+ public static function compareYearWithMonth($compareYearWithMonth = true)
+ {
+ static::$compareYearWithMonth = $compareYearWithMonth;
+ }
+
+ /**
+ * Get the month comparison default behavior.
+ *
+ * @return bool
+ */
+ public static function shouldCompareYearWithMonth()
+ {
+ return static::$compareYearWithMonth;
+ }
+
+ /**
+ * Creates a DateTimeZone from a string, DateTimeZone or integer offset.
+ *
+ * @param \DateTimeZone|string|int|null $object
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return \DateTimeZone
+ */
+ protected static function safeCreateDateTimeZone($object)
+ {
+ if ($object === null) {
+ // Don't return null... avoid Bug #52063 in PHP <5.3.6
+ return new DateTimeZone(date_default_timezone_get());
+ }
+
+ if ($object instanceof DateTimeZone) {
+ return $object;
+ }
+
+ if (is_numeric($object)) {
+ $tzName = timezone_name_from_abbr(null, $object * 3600, true);
+
+ if ($tzName === false) {
+ throw new InvalidArgumentException('Unknown or bad timezone ('.$object.')');
+ }
+
+ $object = $tzName;
+ }
+
+ $tz = @timezone_open($object = (string) $object);
+
+ if ($tz !== false) {
+ return $tz;
+ }
+
+ // Work-around for a bug fixed in PHP 5.5.10 https://bugs.php.net/bug.php?id=45528
+ // See: https://stackoverflow.com/q/14068594/2646927
+ // @codeCoverageIgnoreStart
+ if (strpos($object, ':') !== false) {
+ try {
+ return static::createFromFormat('O', $object)->getTimezone();
+ } catch (InvalidArgumentException $e) {
+ //
+ }
+ }
+ // @codeCoverageIgnoreEnd
+
+ throw new InvalidArgumentException('Unknown or bad timezone ('.$object.')');
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ //////////////////////////// CONSTRUCTORS /////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Create a new Carbon instance.
+ *
+ * Please see the testing aids section (specifically static::setTestNow())
+ * for more on the possibility of this constructor returning a test instance.
+ *
+ * @param string|null $time
+ * @param \DateTimeZone|string|null $tz
+ */
+ public function __construct($time = null, $tz = null)
+ {
+ // If the class has a test now set and we are trying to create a now()
+ // instance then override as required
+ $isNow = empty($time) || $time === 'now';
+ if (static::hasTestNow() && ($isNow || static::hasRelativeKeywords($time))) {
+ $testInstance = clone static::getTestNow();
+
+ //shift the time according to the given time zone
+ if ($tz !== null && $tz !== static::getTestNow()->getTimezone()) {
+ $testInstance->setTimezone($tz);
+ } else {
+ $tz = $testInstance->getTimezone();
+ }
+
+ if (static::hasRelativeKeywords($time)) {
+ $testInstance->modify($time);
+ }
+
+ $time = $testInstance->format(static::MOCK_DATETIME_FORMAT);
+ }
+
+ $timezone = static::safeCreateDateTimeZone($tz);
+ // @codeCoverageIgnoreStart
+ if ($isNow && !isset($testInstance) && static::isMicrosecondsFallbackEnabled() && (
+ version_compare(PHP_VERSION, '7.1.0-dev', '<')
+ ||
+ version_compare(PHP_VERSION, '7.1.3-dev', '>=') && version_compare(PHP_VERSION, '7.1.4-dev', '<')
+ )
+ ) {
+ // Get microseconds from microtime() if "now" asked and PHP < 7.1 and PHP 7.1.3 if fallback enabled.
+ list($microTime, $timeStamp) = explode(' ', microtime());
+ $dateTime = new DateTime('now', $timezone);
+ $dateTime->setTimestamp($timeStamp); // Use the timestamp returned by microtime as now can happen in the next second
+ $time = $dateTime->format(static::DEFAULT_TO_STRING_FORMAT).substr($microTime, 1, 7);
+ }
+ // @codeCoverageIgnoreEnd
+
+ // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127
+ if (strpos((string) .1, '.') === false) {
+ $locale = setlocale(LC_NUMERIC, '0');
+ setlocale(LC_NUMERIC, 'C');
+ }
+ parent::__construct($time, $timezone);
+ if (isset($locale)) {
+ setlocale(LC_NUMERIC, $locale);
+ }
+ static::setLastErrors(parent::getLastErrors());
+ }
+
+ /**
+ * Create a Carbon instance from a DateTime one.
+ *
+ * @param \DateTime|\DateTimeInterface $date
+ *
+ * @return static
+ */
+ public static function instance($date)
+ {
+ if ($date instanceof static) {
+ return clone $date;
+ }
+
+ static::expectDateTime($date);
+
+ return new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone());
+ }
+
+ /**
+ * Create a carbon instance from a string.
+ *
+ * This is an alias for the constructor that allows better fluent syntax
+ * as it allows you to do Carbon::parse('Monday next week')->fn() rather
+ * than (new Carbon('Monday next week'))->fn().
+ *
+ * @param string|null $time
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @return static
+ */
+ public static function parse($time = null, $tz = null)
+ {
+ return new static($time, $tz);
+ }
+
+ /**
+ * Get a Carbon instance for the current date and time.
+ *
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @return static
+ */
+ public static function now($tz = null)
+ {
+ return new static(null, $tz);
+ }
+
+ /**
+ * Create a Carbon instance for today.
+ *
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @return static
+ */
+ public static function today($tz = null)
+ {
+ return static::parse('today', $tz);
+ }
+
+ /**
+ * Create a Carbon instance for tomorrow.
+ *
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @return static
+ */
+ public static function tomorrow($tz = null)
+ {
+ return static::parse('tomorrow', $tz);
+ }
+
+ /**
+ * Create a Carbon instance for yesterday.
+ *
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @return static
+ */
+ public static function yesterday($tz = null)
+ {
+ return static::parse('yesterday', $tz);
+ }
+
+ /**
+ * Create a Carbon instance for the greatest supported date.
+ *
+ * @return static
+ */
+ public static function maxValue()
+ {
+ if (self::$PHPIntSize === 4) {
+ // 32 bit
+ return static::createFromTimestamp(PHP_INT_MAX); // @codeCoverageIgnore
+ }
+
+ // 64 bit
+ return static::create(9999, 12, 31, 23, 59, 59);
+ }
+
+ /**
+ * Create a Carbon instance for the lowest supported date.
+ *
+ * @return static
+ */
+ public static function minValue()
+ {
+ if (self::$PHPIntSize === 4) {
+ // 32 bit
+ return static::createFromTimestamp(~PHP_INT_MAX); // @codeCoverageIgnore
+ }
+
+ // 64 bit
+ return static::create(1, 1, 1, 0, 0, 0);
+ }
+
+ /**
+ * Create a new Carbon instance from a specific date and time.
+ *
+ * If any of $year, $month or $day are set to null their now() values will
+ * be used.
+ *
+ * If $hour is null it will be set to its now() value and the default
+ * values for $minute and $second will be their now() values.
+ *
+ * If $hour is not null then the default values for $minute and $second
+ * will be 0.
+ *
+ * @param int|null $year
+ * @param int|null $month
+ * @param int|null $day
+ * @param int|null $hour
+ * @param int|null $minute
+ * @param int|null $second
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return static
+ */
+ public static function create($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null)
+ {
+ $now = static::hasTestNow() ? static::getTestNow() : static::now($tz);
+
+ $defaults = array_combine(array(
+ 'year',
+ 'month',
+ 'day',
+ 'hour',
+ 'minute',
+ 'second',
+ ), explode('-', $now->format('Y-n-j-G-i-s')));
+
+ $year = $year === null ? $defaults['year'] : $year;
+ $month = $month === null ? $defaults['month'] : $month;
+ $day = $day === null ? $defaults['day'] : $day;
+
+ if ($hour === null) {
+ $hour = $defaults['hour'];
+ $minute = $minute === null ? $defaults['minute'] : $minute;
+ $second = $second === null ? $defaults['second'] : $second;
+ } else {
+ $minute = $minute === null ? 0 : $minute;
+ $second = $second === null ? 0 : $second;
+ }
+
+ $fixYear = null;
+
+ if ($year < 0) {
+ $fixYear = $year;
+ $year = 0;
+ } elseif ($year > 9999) {
+ $fixYear = $year - 9999;
+ $year = 9999;
+ }
+
+ $instance = static::createFromFormat('!Y-n-j G:i:s', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz);
+
+ if ($fixYear !== null) {
+ $instance->addYears($fixYear);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Create a new safe Carbon instance from a specific date and time.
+ *
+ * If any of $year, $month or $day are set to null their now() values will
+ * be used.
+ *
+ * If $hour is null it will be set to its now() value and the default
+ * values for $minute and $second will be their now() values.
+ *
+ * If $hour is not null then the default values for $minute and $second
+ * will be 0.
+ *
+ * If one of the set values is not valid, an \InvalidArgumentException
+ * will be thrown.
+ *
+ * @param int|null $year
+ * @param int|null $month
+ * @param int|null $day
+ * @param int|null $hour
+ * @param int|null $minute
+ * @param int|null $second
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @throws \Carbon\Exceptions\InvalidDateException|\InvalidArgumentException
+ *
+ * @return static
+ */
+ public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null)
+ {
+ $fields = array(
+ 'year' => array(0, 9999),
+ 'month' => array(0, 12),
+ 'day' => array(0, 31),
+ 'hour' => array(0, 24),
+ 'minute' => array(0, 59),
+ 'second' => array(0, 59),
+ );
+
+ foreach ($fields as $field => $range) {
+ if ($$field !== null && (!is_int($$field) || $$field < $range[0] || $$field > $range[1])) {
+ throw new InvalidDateException($field, $$field);
+ }
+ }
+
+ $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz);
+
+ foreach (array_reverse($fields) as $field => $range) {
+ if ($$field !== null && (!is_int($$field) || $$field !== $instance->$field)) {
+ throw new InvalidDateException($field, $$field);
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Create a Carbon instance from just a date. The time portion is set to now.
+ *
+ * @param int|null $year
+ * @param int|null $month
+ * @param int|null $day
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return static
+ */
+ public static function createFromDate($year = null, $month = null, $day = null, $tz = null)
+ {
+ return static::create($year, $month, $day, null, null, null, $tz);
+ }
+
+ /**
+ * Create a Carbon instance from just a date. The time portion is set to midnight.
+ *
+ * @param int|null $year
+ * @param int|null $month
+ * @param int|null $day
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @return static
+ */
+ public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null)
+ {
+ return static::create($year, $month, $day, 0, 0, 0, $tz);
+ }
+
+ /**
+ * Create a Carbon instance from just a time. The date portion is set to today.
+ *
+ * @param int|null $hour
+ * @param int|null $minute
+ * @param int|null $second
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return static
+ */
+ public static function createFromTime($hour = null, $minute = null, $second = null, $tz = null)
+ {
+ return static::create(null, null, null, $hour, $minute, $second, $tz);
+ }
+
+ /**
+ * Create a Carbon instance from a time string. The date portion is set to today.
+ *
+ * @param string $time
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return static
+ */
+ public static function createFromTimeString($time, $tz = null)
+ {
+ return static::today($tz)->setTimeFromTimeString($time);
+ }
+
+ private static function createFromFormatAndTimezone($format, $time, $tz)
+ {
+ return $tz !== null
+ ? parent::createFromFormat($format, $time, static::safeCreateDateTimeZone($tz))
+ : parent::createFromFormat($format, $time);
+ }
+
+ /**
+ * Create a Carbon instance from a specific format.
+ *
+ * @param string $format Datetime format
+ * @param string $time
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return static
+ */
+ public static function createFromFormat($format, $time, $tz = null)
+ {
+ // First attempt to create an instance, so that error messages are based on the unmodified format.
+ $date = self::createFromFormatAndTimezone($format, $time, $tz);
+ $lastErrors = parent::getLastErrors();
+
+ if (($mock = static::getTestNow()) && ($date instanceof DateTime || $date instanceof DateTimeInterface)) {
+ // Set timezone from mock if custom timezone was neither given directly nor as a part of format.
+ // First let's skip the part that will be ignored by the parser.
+ $nonEscaped = '(?getTimezone();
+ }
+
+ // Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag.
+ if (!preg_match("/{$nonEscaped}[!|]/", $format)) {
+ $format = static::MOCK_DATETIME_FORMAT.' '.$format;
+ $time = $mock->format(static::MOCK_DATETIME_FORMAT).' '.$time;
+ }
+
+ // Regenerate date from the modified format to base result on the mocked instance instead of now.
+ $date = self::createFromFormatAndTimezone($format, $time, $tz);
+ }
+
+ if ($date instanceof DateTime || $date instanceof DateTimeInterface) {
+ $instance = static::instance($date);
+ $instance::setLastErrors($lastErrors);
+
+ return $instance;
+ }
+
+ throw new InvalidArgumentException(implode(PHP_EOL, $lastErrors['errors']));
+ }
+
+ /**
+ * Set last errors.
+ *
+ * @param array $lastErrors
+ *
+ * @return void
+ */
+ private static function setLastErrors(array $lastErrors)
+ {
+ static::$lastErrors = $lastErrors;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getLastErrors()
+ {
+ return static::$lastErrors;
+ }
+
+ /**
+ * Create a Carbon instance from a timestamp.
+ *
+ * @param int $timestamp
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @return static
+ */
+ public static function createFromTimestamp($timestamp, $tz = null)
+ {
+ return static::today($tz)->setTimestamp($timestamp);
+ }
+
+ /**
+ * Create a Carbon instance from a timestamp in milliseconds.
+ *
+ * @param int $timestamp
+ * @param \DateTimeZone|string|null $tz
+ *
+ * @return static
+ */
+ public static function createFromTimestampMs($timestamp, $tz = null)
+ {
+ return static::createFromFormat('U.u', sprintf('%F', $timestamp / 1000))
+ ->setTimezone($tz);
+ }
+
+ /**
+ * Create a Carbon instance from an UTC timestamp.
+ *
+ * @param int $timestamp
+ *
+ * @return static
+ */
+ public static function createFromTimestampUTC($timestamp)
+ {
+ return new static('@'.$timestamp);
+ }
+
+ /**
+ * Make a Carbon instance from given variable if possible.
+ *
+ * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals
+ * and recurrences). Throw an exception for invalid format, but otherwise return null.
+ *
+ * @param mixed $var
+ *
+ * @return static|null
+ */
+ public static function make($var)
+ {
+ if ($var instanceof DateTime || $var instanceof DateTimeInterface) {
+ return static::instance($var);
+ }
+
+ if (is_string($var)) {
+ $var = trim($var);
+ $first = substr($var, 0, 1);
+
+ if (is_string($var) && $first !== 'P' && $first !== 'R' && preg_match('/[a-z0-9]/i', $var)) {
+ return static::parse($var);
+ }
+ }
+ }
+
+ /**
+ * Get a copy of the instance.
+ *
+ * @return static
+ */
+ public function copy()
+ {
+ return clone $this;
+ }
+
+ /**
+ * Returns a present instance in the same timezone.
+ *
+ * @return static
+ */
+ public function nowWithSameTz()
+ {
+ return static::now($this->getTimezone());
+ }
+
+ /**
+ * Throws an exception if the given object is not a DateTime and does not implement DateTimeInterface
+ * and not in $other.
+ *
+ * @param mixed $date
+ * @param string|array $other
+ *
+ * @throws \InvalidArgumentException
+ */
+ protected static function expectDateTime($date, $other = array())
+ {
+ $message = 'Expected ';
+ foreach ((array) $other as $expect) {
+ $message .= "{$expect}, ";
+ }
+
+ if (!$date instanceof DateTime && !$date instanceof DateTimeInterface) {
+ throw new InvalidArgumentException(
+ $message.'DateTime or DateTimeInterface, '.
+ (is_object($date) ? get_class($date) : gettype($date)).' given'
+ );
+ }
+ }
+
+ /**
+ * Return the Carbon instance passed through, a now instance in the same timezone
+ * if null given or parse the input if string given.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ *
+ * @return static
+ */
+ protected function resolveCarbon($date = null)
+ {
+ if (!$date) {
+ return $this->nowWithSameTz();
+ }
+
+ if (is_string($date)) {
+ return static::parse($date, $this->getTimezone());
+ }
+
+ static::expectDateTime($date, array('null', 'string'));
+
+ return $date instanceof self ? $date : static::instance($date);
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ ///////////////////////// GETTERS AND SETTERS /////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Get a part of the Carbon object
+ *
+ * @param string $name
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string|int|bool|\DateTimeZone
+ */
+ public function __get($name)
+ {
+ static $formats = array(
+ 'year' => 'Y',
+ 'yearIso' => 'o',
+ 'month' => 'n',
+ 'day' => 'j',
+ 'hour' => 'G',
+ 'minute' => 'i',
+ 'second' => 's',
+ 'micro' => 'u',
+ 'dayOfWeek' => 'w',
+ 'dayOfWeekIso' => 'N',
+ 'dayOfYear' => 'z',
+ 'weekOfYear' => 'W',
+ 'daysInMonth' => 't',
+ 'timestamp' => 'U',
+ 'englishDayOfWeek' => 'l',
+ 'shortEnglishDayOfWeek' => 'D',
+ 'englishMonth' => 'F',
+ 'shortEnglishMonth' => 'M',
+ 'localeDayOfWeek' => '%A',
+ 'shortLocaleDayOfWeek' => '%a',
+ 'localeMonth' => '%B',
+ 'shortLocaleMonth' => '%b',
+ );
+
+ switch (true) {
+ case isset($formats[$name]):
+ $format = $formats[$name];
+ $method = substr($format, 0, 1) === '%' ? 'formatLocalized' : 'format';
+ $value = $this->$method($format);
+
+ return is_numeric($value) ? (int) $value : $value;
+
+ case $name === 'weekOfMonth':
+ return (int) ceil($this->day / static::DAYS_PER_WEEK);
+
+ case $name === 'weekNumberInMonth':
+ return (int) ceil(($this->day + $this->copy()->startOfMonth()->dayOfWeek - 1) / static::DAYS_PER_WEEK);
+
+ case $name === 'age':
+ return $this->diffInYears();
+
+ case $name === 'quarter':
+ return (int) ceil($this->month / static::MONTHS_PER_QUARTER);
+
+ case $name === 'offset':
+ return $this->getOffset();
+
+ case $name === 'offsetHours':
+ return $this->getOffset() / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR;
+
+ case $name === 'dst':
+ return $this->format('I') === '1';
+
+ case $name === 'local':
+ return $this->getOffset() === $this->copy()->setTimezone(date_default_timezone_get())->getOffset();
+
+ case $name === 'utc':
+ return $this->getOffset() === 0;
+
+ case $name === 'timezone' || $name === 'tz':
+ return $this->getTimezone();
+
+ case $name === 'timezoneName' || $name === 'tzName':
+ return $this->getTimezone()->getName();
+
+ default:
+ throw new InvalidArgumentException(sprintf("Unknown getter '%s'", $name));
+ }
+ }
+
+ /**
+ * Check if an attribute exists on the object
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ try {
+ $this->__get($name);
+ } catch (InvalidArgumentException $e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Set a part of the Carbon object
+ *
+ * @param string $name
+ * @param string|int|\DateTimeZone $value
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'year':
+ case 'month':
+ case 'day':
+ case 'hour':
+ case 'minute':
+ case 'second':
+ list($year, $month, $day, $hour, $minute, $second) = explode('-', $this->format('Y-n-j-G-i-s'));
+ $$name = $value;
+ $this->setDateTime($year, $month, $day, $hour, $minute, $second);
+ break;
+
+ case 'timestamp':
+ parent::setTimestamp($value);
+ break;
+
+ case 'timezone':
+ case 'tz':
+ $this->setTimezone($value);
+ break;
+
+ default:
+ throw new InvalidArgumentException(sprintf("Unknown setter '%s'", $name));
+ }
+ }
+
+ /**
+ * Set the instance's year
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function year($value)
+ {
+ $this->year = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set the instance's month
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function month($value)
+ {
+ $this->month = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set the instance's day
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function day($value)
+ {
+ $this->day = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set the instance's hour
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function hour($value)
+ {
+ $this->hour = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set the instance's minute
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function minute($value)
+ {
+ $this->minute = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set the instance's second
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function second($value)
+ {
+ $this->second = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the current date of the DateTime object to a different date.
+ * Calls modify as a workaround for a php bug
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ *
+ * @return static
+ *
+ * @see https://github.com/briannesbitt/Carbon/issues/539
+ * @see https://bugs.php.net/bug.php?id=63863
+ */
+ public function setDate($year, $month, $day)
+ {
+ $this->modify('+0 day');
+
+ return parent::setDate($year, $month, $day);
+ }
+
+ /**
+ * Set the date and time all together
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @param int $hour
+ * @param int $minute
+ * @param int $second
+ *
+ * @return static
+ */
+ public function setDateTime($year, $month, $day, $hour, $minute, $second = 0)
+ {
+ return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second);
+ }
+
+ /**
+ * Set the time by time string
+ *
+ * @param string $time
+ *
+ * @return static
+ */
+ public function setTimeFromTimeString($time)
+ {
+ if (strpos($time, ':') === false) {
+ $time .= ':0';
+ }
+
+ return $this->modify($time);
+ }
+
+ /**
+ * Set the instance's timestamp
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function timestamp($value)
+ {
+ return $this->setTimestamp($value);
+ }
+
+ /**
+ * Alias for setTimezone()
+ *
+ * @param \DateTimeZone|string $value
+ *
+ * @return static
+ */
+ public function timezone($value)
+ {
+ return $this->setTimezone($value);
+ }
+
+ /**
+ * Alias for setTimezone()
+ *
+ * @param \DateTimeZone|string $value
+ *
+ * @return static
+ */
+ public function tz($value)
+ {
+ return $this->setTimezone($value);
+ }
+
+ /**
+ * Set the instance's timezone from a string or object
+ *
+ * @param \DateTimeZone|string $value
+ *
+ * @return static
+ */
+ public function setTimezone($value)
+ {
+ parent::setTimezone(static::safeCreateDateTimeZone($value));
+ // https://bugs.php.net/bug.php?id=72338
+ // just workaround on this bug
+ $this->getTimestamp();
+
+ return $this;
+ }
+
+ /**
+ * Set the year, month, and date for this instance to that of the passed instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface $date
+ *
+ * @return static
+ */
+ public function setDateFrom($date)
+ {
+ $date = static::instance($date);
+
+ $this->setDate($date->year, $date->month, $date->day);
+
+ return $this;
+ }
+
+ /**
+ * Set the hour, day, and time for this instance to that of the passed instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface $date
+ *
+ * @return static
+ */
+ public function setTimeFrom($date)
+ {
+ $date = static::instance($date);
+
+ $this->setTime($date->hour, $date->minute, $date->second);
+
+ return $this;
+ }
+
+ /**
+ * Get the days of the week
+ *
+ * @return array
+ */
+ public static function getDays()
+ {
+ return static::$days;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /////////////////////// WEEK SPECIAL DAYS /////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the first day of week
+ *
+ * @return int
+ */
+ public static function getWeekStartsAt()
+ {
+ return static::$weekStartsAt;
+ }
+
+ /**
+ * Set the first day of week
+ *
+ * @param int $day week start day
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return void
+ */
+ public static function setWeekStartsAt($day)
+ {
+ if ($day > static::SATURDAY || $day < static::SUNDAY) {
+ throw new InvalidArgumentException('Day of a week should be greater than or equal to 0 and less than or equal to 6.');
+ }
+
+ static::$weekStartsAt = $day;
+ }
+
+ /**
+ * Get the last day of week
+ *
+ * @return int
+ */
+ public static function getWeekEndsAt()
+ {
+ return static::$weekEndsAt;
+ }
+
+ /**
+ * Set the last day of week
+ *
+ * @param int $day
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return void
+ */
+ public static function setWeekEndsAt($day)
+ {
+ if ($day > static::SATURDAY || $day < static::SUNDAY) {
+ throw new InvalidArgumentException('Day of a week should be greater than or equal to 0 and less than or equal to 6.');
+ }
+
+ static::$weekEndsAt = $day;
+ }
+
+ /**
+ * Get weekend days
+ *
+ * @return array
+ */
+ public static function getWeekendDays()
+ {
+ return static::$weekendDays;
+ }
+
+ /**
+ * Set weekend days
+ *
+ * @param array $days
+ *
+ * @return void
+ */
+ public static function setWeekendDays($days)
+ {
+ static::$weekendDays = $days;
+ }
+
+ /**
+ * get midday/noon hour
+ *
+ * @return int
+ */
+ public static function getMidDayAt()
+ {
+ return static::$midDayAt;
+ }
+
+ /**
+ * Set midday/noon hour
+ *
+ * @param int $hour midday hour
+ *
+ * @return void
+ */
+ public static function setMidDayAt($hour)
+ {
+ static::$midDayAt = $hour;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ ///////////////////////// TESTING AIDS ////////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Set a Carbon instance (real or mock) to be returned when a "now"
+ * instance is created. The provided instance will be returned
+ * specifically under the following conditions:
+ * - A call to the static now() method, ex. Carbon::now()
+ * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)
+ * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now')
+ * - When a string containing the desired time is passed to Carbon::parse().
+ *
+ * Note the timezone parameter was left out of the examples above and
+ * has no affect as the mock value will be returned regardless of its value.
+ *
+ * To clear the test instance call this method using the default
+ * parameter of null.
+ *
+ * @param \Carbon\Carbon|null $testNow real or mock Carbon instance
+ * @param \Carbon\Carbon|string|null $testNow
+ */
+ public static function setTestNow($testNow = null)
+ {
+ static::$testNow = is_string($testNow) ? static::parse($testNow) : $testNow;
+ }
+
+ /**
+ * Get the Carbon instance (real or mock) to be returned when a "now"
+ * instance is created.
+ *
+ * @return static the current instance used for testing
+ */
+ public static function getTestNow()
+ {
+ return static::$testNow;
+ }
+
+ /**
+ * Determine if there is a valid test instance set. A valid test instance
+ * is anything that is not null.
+ *
+ * @return bool true if there is a test instance, otherwise false
+ */
+ public static function hasTestNow()
+ {
+ return static::getTestNow() !== null;
+ }
+
+ /**
+ * Determine if a time string will produce a relative date.
+ *
+ * @param string $time
+ *
+ * @return bool true if time match a relative date, false if absolute or invalid time string
+ */
+ public static function hasRelativeKeywords($time)
+ {
+ if (strtotime($time) === false) {
+ return false;
+ }
+
+ $date1 = new DateTime('2000-01-01T00:00:00Z');
+ $date1->modify($time);
+ $date2 = new DateTime('2001-12-25T00:00:00Z');
+ $date2->modify($time);
+
+ return $date1 != $date2;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /////////////////////// LOCALIZATION //////////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Initialize the translator instance if necessary.
+ *
+ * @return \Symfony\Component\Translation\TranslatorInterface
+ */
+ protected static function translator()
+ {
+ if (static::$translator === null) {
+ static::$translator = Translator::get();
+ }
+
+ return static::$translator;
+ }
+
+ /**
+ * Get the translator instance in use
+ *
+ * @return \Symfony\Component\Translation\TranslatorInterface
+ */
+ public static function getTranslator()
+ {
+ return static::translator();
+ }
+
+ /**
+ * Set the translator instance to use
+ *
+ * @param \Symfony\Component\Translation\TranslatorInterface $translator
+ *
+ * @return void
+ */
+ public static function setTranslator(TranslatorInterface $translator)
+ {
+ static::$translator = $translator;
+ }
+
+ /**
+ * Get the current translator locale
+ *
+ * @return string
+ */
+ public static function getLocale()
+ {
+ return static::translator()->getLocale();
+ }
+
+ /**
+ * Set the current translator locale and indicate if the source locale file exists
+ *
+ * @param string $locale locale ex. en
+ *
+ * @return bool
+ */
+ public static function setLocale($locale)
+ {
+ return static::translator()->setLocale($locale) !== false;
+ }
+
+ /**
+ * Set the current locale to the given, execute the passed function, reset the locale to previous one,
+ * then return the result of the closure (or null if the closure was void).
+ *
+ * @param string $locale locale ex. en
+ *
+ * @return mixed
+ */
+ public static function executeWithLocale($locale, $func)
+ {
+ $currentLocale = static::getLocale();
+ $result = call_user_func($func, static::setLocale($locale) ? static::getLocale() : false, static::translator());
+ static::setLocale($currentLocale);
+
+ return $result;
+ }
+
+ /**
+ * Returns true if the given locale is internally supported and has short-units support.
+ * Support is considered enabled if either year, day or hour has a short variant translated.
+ *
+ * @param string $locale locale ex. en
+ *
+ * @return bool
+ */
+ public static function localeHasShortUnits($locale)
+ {
+ return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
+ return $newLocale &&
+ (
+ ($y = $translator->trans('y')) !== 'y' &&
+ $y !== $translator->trans('year')
+ ) || (
+ ($y = $translator->trans('d')) !== 'd' &&
+ $y !== $translator->trans('day')
+ ) || (
+ ($y = $translator->trans('h')) !== 'h' &&
+ $y !== $translator->trans('hour')
+ );
+ });
+ }
+
+ /**
+ * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after).
+ * Support is considered enabled if the 4 sentences are translated in the given locale.
+ *
+ * @param string $locale locale ex. en
+ *
+ * @return bool
+ */
+ public static function localeHasDiffSyntax($locale)
+ {
+ return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
+ return $newLocale &&
+ $translator->trans('ago') !== 'ago' &&
+ $translator->trans('from_now') !== 'from_now' &&
+ $translator->trans('before') !== 'before' &&
+ $translator->trans('after') !== 'after';
+ });
+ }
+
+ /**
+ * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow).
+ * Support is considered enabled if the 3 words are translated in the given locale.
+ *
+ * @param string $locale locale ex. en
+ *
+ * @return bool
+ */
+ public static function localeHasDiffOneDayWords($locale)
+ {
+ return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
+ return $newLocale &&
+ $translator->trans('diff_now') !== 'diff_now' &&
+ $translator->trans('diff_yesterday') !== 'diff_yesterday' &&
+ $translator->trans('diff_tomorrow') !== 'diff_tomorrow';
+ });
+ }
+
+ /**
+ * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow).
+ * Support is considered enabled if the 2 words are translated in the given locale.
+ *
+ * @param string $locale locale ex. en
+ *
+ * @return bool
+ */
+ public static function localeHasDiffTwoDayWords($locale)
+ {
+ return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
+ return $newLocale &&
+ $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' &&
+ $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow';
+ });
+ }
+
+ /**
+ * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X).
+ * Support is considered enabled if the 4 sentences are translated in the given locale.
+ *
+ * @param string $locale locale ex. en
+ *
+ * @return bool
+ */
+ public static function localeHasPeriodSyntax($locale)
+ {
+ return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
+ return $newLocale &&
+ $translator->trans('period_recurrences') !== 'period_recurrences' &&
+ $translator->trans('period_interval') !== 'period_interval' &&
+ $translator->trans('period_start_date') !== 'period_start_date' &&
+ $translator->trans('period_end_date') !== 'period_end_date';
+ });
+ }
+
+ /**
+ * Returns the list of internally available locales and already loaded custom locales.
+ * (It will ignore custom translator dynamic loading.)
+ *
+ * @return array
+ */
+ public static function getAvailableLocales()
+ {
+ $translator = static::translator();
+ $locales = array();
+ if ($translator instanceof Translator) {
+ foreach (glob(__DIR__.'/Lang/*.php') as $file) {
+ $locales[] = substr($file, strrpos($file, '/') + 1, -4);
+ }
+
+ $locales = array_unique(array_merge($locales, array_keys($translator->getMessages())));
+ }
+
+ return $locales;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /////////////////////// STRING FORMATTING /////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Set if UTF8 will be used for localized date/time
+ *
+ * @param bool $utf8
+ */
+ public static function setUtf8($utf8)
+ {
+ static::$utf8 = $utf8;
+ }
+
+ /**
+ * Format the instance with the current locale. You can set the current
+ * locale using setlocale() http://php.net/setlocale.
+ *
+ * @param string $format
+ *
+ * @return string
+ */
+ public function formatLocalized($format)
+ {
+ // Check for Windows to find and replace the %e modifier correctly.
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ $format = preg_replace('#(?toDateTimeString()));
+
+ return static::$utf8 ? utf8_encode($formatted) : $formatted;
+ }
+
+ /**
+ * Reset the format used to the default when type juggling a Carbon instance to a string
+ *
+ * @return void
+ */
+ public static function resetToStringFormat()
+ {
+ static::setToStringFormat(static::DEFAULT_TO_STRING_FORMAT);
+ }
+
+ /**
+ * Set the default format used when type juggling a Carbon instance to a string
+ *
+ * @param string|Closure $format
+ *
+ * @return void
+ */
+ public static function setToStringFormat($format)
+ {
+ static::$toStringFormat = $format;
+ }
+
+ /**
+ * Format the instance as a string using the set format
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $format = static::$toStringFormat;
+
+ return $this->format($format instanceof Closure ? $format($this) : $format);
+ }
+
+ /**
+ * Format the instance as date
+ *
+ * @return string
+ */
+ public function toDateString()
+ {
+ return $this->format('Y-m-d');
+ }
+
+ /**
+ * Format the instance as a readable date
+ *
+ * @return string
+ */
+ public function toFormattedDateString()
+ {
+ return $this->format('M j, Y');
+ }
+
+ /**
+ * Format the instance as time
+ *
+ * @return string
+ */
+ public function toTimeString()
+ {
+ return $this->format('H:i:s');
+ }
+
+ /**
+ * Format the instance as date and time
+ *
+ * @return string
+ */
+ public function toDateTimeString()
+ {
+ return $this->format('Y-m-d H:i:s');
+ }
+
+ /**
+ * Format the instance as date and time T-separated with no timezone
+ *
+ * @example
+ * ```
+ * echo Carbon::now()->toDateTimeLocalString();
+ * ```
+ *
+ * @return string
+ */
+ public function toDateTimeLocalString()
+ {
+ return $this->format('Y-m-d\TH:i:s');
+ }
+
+ /**
+ * Format the instance with day, date and time
+ *
+ * @return string
+ */
+ public function toDayDateTimeString()
+ {
+ return $this->format('D, M j, Y g:i A');
+ }
+
+ /**
+ * Format the instance as ATOM
+ *
+ * @return string
+ */
+ public function toAtomString()
+ {
+ return $this->format(static::ATOM);
+ }
+
+ /**
+ * Format the instance as COOKIE
+ *
+ * @return string
+ */
+ public function toCookieString()
+ {
+ return $this->format(static::COOKIE);
+ }
+
+ /**
+ * Format the instance as ISO8601
+ *
+ * @return string
+ */
+ public function toIso8601String()
+ {
+ return $this->toAtomString();
+ }
+
+ /**
+ * Format the instance as RFC822
+ *
+ * @return string
+ */
+ public function toRfc822String()
+ {
+ return $this->format(static::RFC822);
+ }
+
+ /**
+ * Convert the instance to UTC and return as Zulu ISO8601
+ *
+ * @return string
+ */
+ public function toIso8601ZuluString()
+ {
+ return $this->copy()->setTimezone('UTC')->format('Y-m-d\TH:i:s\Z');
+ }
+
+ /**
+ * Format the instance as RFC850
+ *
+ * @return string
+ */
+ public function toRfc850String()
+ {
+ return $this->format(static::RFC850);
+ }
+
+ /**
+ * Format the instance as RFC1036
+ *
+ * @return string
+ */
+ public function toRfc1036String()
+ {
+ return $this->format(static::RFC1036);
+ }
+
+ /**
+ * Format the instance as RFC1123
+ *
+ * @return string
+ */
+ public function toRfc1123String()
+ {
+ return $this->format(static::RFC1123);
+ }
+
+ /**
+ * Format the instance as RFC2822
+ *
+ * @return string
+ */
+ public function toRfc2822String()
+ {
+ return $this->format(static::RFC2822);
+ }
+
+ /**
+ * Format the instance as RFC3339
+ *
+ * @return string
+ */
+ public function toRfc3339String()
+ {
+ return $this->format(static::RFC3339);
+ }
+
+ /**
+ * Format the instance as RSS
+ *
+ * @return string
+ */
+ public function toRssString()
+ {
+ return $this->format(static::RSS);
+ }
+
+ /**
+ * Format the instance as W3C
+ *
+ * @return string
+ */
+ public function toW3cString()
+ {
+ return $this->format(static::W3C);
+ }
+
+ /**
+ * Format the instance as RFC7231
+ *
+ * @return string
+ */
+ public function toRfc7231String()
+ {
+ return $this->copy()
+ ->setTimezone('GMT')
+ ->format(static::RFC7231_FORMAT);
+ }
+
+ /**
+ * Get default array representation
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return array(
+ 'year' => $this->year,
+ 'month' => $this->month,
+ 'day' => $this->day,
+ 'dayOfWeek' => $this->dayOfWeek,
+ 'dayOfYear' => $this->dayOfYear,
+ 'hour' => $this->hour,
+ 'minute' => $this->minute,
+ 'second' => $this->second,
+ 'micro' => $this->micro,
+ 'timestamp' => $this->timestamp,
+ 'formatted' => $this->format(self::DEFAULT_TO_STRING_FORMAT),
+ 'timezone' => $this->timezone,
+ );
+ }
+
+ /**
+ * Get default object representation.
+ *
+ * @example
+ * ```
+ * var_dump(Carbon::now()->toObject());
+ * ```
+ *
+ * @return object
+ */
+ public function toObject()
+ {
+ return (object) $this->toArray();
+ }
+
+ /**
+ * Returns english human readable complete date string.
+ *
+ * @example
+ * ```
+ * echo Carbon::now()->toString();
+ * ```
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return $this->format('D M j Y H:i:s \G\M\TO');
+ }
+
+ /**
+ * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept:
+ * 1977-04-22T01:00:00-05:00).
+ *
+ * @example
+ * ```
+ * echo Carbon::now('America/Toronto')->toISOString() . "\n";
+ * echo Carbon::now('America/Toronto')->toISOString(true) . "\n";
+ * ```
+ *
+ * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC.
+ *
+ * @return null|string
+ */
+ public function toISOString($keepOffset = false)
+ {
+ if ($this->year === 0) {
+ return null;
+ }
+
+ $year = $this->year < 0 || $this->year > 9999
+ ? ($this->year < 0 ? '-' : '+').str_pad(abs($this->year), 6, '0', STR_PAD_LEFT)
+ : str_pad($this->year, 4, '0', STR_PAD_LEFT);
+ $tz = $keepOffset ? $this->format('P') : 'Z';
+ $date = $keepOffset ? $this : $this->copy()->setTimezone('UTC');
+
+ return $year.$date->format('-m-d\TH:i:s.u').$tz;
+ }
+
+ /**
+ * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone.
+ *
+ * @example
+ * ```
+ * echo Carbon::now('America/Toronto')->toJSON();
+ * ```
+ *
+ * @return null|string
+ */
+ public function toJSON()
+ {
+ return $this->toISOString();
+ }
+
+ /**
+ * Return native DateTime PHP object matching the current instance.
+ *
+ * @example
+ * ```
+ * var_dump(Carbon::now()->toDateTime());
+ * ```
+ *
+ * @return DateTime
+ */
+ public function toDateTime()
+ {
+ return new DateTime($this->format('Y-m-d H:i:s.u'), $this->getTimezone());
+ }
+
+ /**
+ * @alias toDateTime
+ *
+ * Return native DateTime PHP object matching the current instance.
+ *
+ * @example
+ * ```
+ * var_dump(Carbon::now()->toDate());
+ * ```
+ *
+ * @return DateTime
+ */
+ public function toDate()
+ {
+ return $this->toDateTime();
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ ////////////////////////// COMPARISONS ////////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Determines if the instance is equal to another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @return bool
+ */
+ public function eq($date)
+ {
+ return $this == $date;
+ }
+
+ /**
+ * Determines if the instance is equal to another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see eq()
+ *
+ * @return bool
+ */
+ public function equalTo($date)
+ {
+ return $this->eq($date);
+ }
+
+ /**
+ * Determines if the instance is not equal to another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @return bool
+ */
+ public function ne($date)
+ {
+ return !$this->eq($date);
+ }
+
+ /**
+ * Determines if the instance is not equal to another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see ne()
+ *
+ * @return bool
+ */
+ public function notEqualTo($date)
+ {
+ return $this->ne($date);
+ }
+
+ /**
+ * Determines if the instance is greater (after) than another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @return bool
+ */
+ public function gt($date)
+ {
+ return $this > $date;
+ }
+
+ /**
+ * Determines if the instance is greater (after) than another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see gt()
+ *
+ * @return bool
+ */
+ public function greaterThan($date)
+ {
+ return $this->gt($date);
+ }
+
+ /**
+ * Determines if the instance is greater (after) than another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see gt()
+ *
+ * @return bool
+ */
+ public function isAfter($date)
+ {
+ return $this->gt($date);
+ }
+
+ /**
+ * Determines if the instance is greater (after) than or equal to another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @return bool
+ */
+ public function gte($date)
+ {
+ return $this >= $date;
+ }
+
+ /**
+ * Determines if the instance is greater (after) than or equal to another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see gte()
+ *
+ * @return bool
+ */
+ public function greaterThanOrEqualTo($date)
+ {
+ return $this->gte($date);
+ }
+
+ /**
+ * Determines if the instance is less (before) than another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @return bool
+ */
+ public function lt($date)
+ {
+ return $this < $date;
+ }
+
+ /**
+ * Determines if the instance is less (before) than another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see lt()
+ *
+ * @return bool
+ */
+ public function lessThan($date)
+ {
+ return $this->lt($date);
+ }
+
+ /**
+ * Determines if the instance is less (before) than another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see lt()
+ *
+ * @return bool
+ */
+ public function isBefore($date)
+ {
+ return $this->lt($date);
+ }
+
+ /**
+ * Determines if the instance is less (before) or equal to another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @return bool
+ */
+ public function lte($date)
+ {
+ return $this <= $date;
+ }
+
+ /**
+ * Determines if the instance is less (before) or equal to another
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see lte()
+ *
+ * @return bool
+ */
+ public function lessThanOrEqualTo($date)
+ {
+ return $this->lte($date);
+ }
+
+ /**
+ * Determines if the instance is between two others
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2
+ * @param bool $equal Indicates if an equal to comparison should be done
+ *
+ * @return bool
+ */
+ public function between($date1, $date2, $equal = true)
+ {
+ if ($date1->gt($date2)) {
+ $temp = $date1;
+ $date1 = $date2;
+ $date2 = $temp;
+ }
+
+ if ($equal) {
+ return $this->gte($date1) && $this->lte($date2);
+ }
+
+ 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);
+ }
+
+ /**
+ * Determines if the instance is between two others
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2
+ * @param bool $equal Indicates if a > and < comparison should be used or <= or >=
+ *
+ * @return bool
+ */
+ public function isBetween($date1, $date2, $equal = true)
+ {
+ return $this->between($date1, $date2, $equal);
+ }
+
+ /**
+ * Get the closest date from the instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2
+ *
+ * @return static
+ */
+ public function closest($date1, $date2)
+ {
+ return $this->floatDiffInSeconds($date1) < $this->floatDiffInSeconds($date2) ? $date1 : $date2;
+ }
+
+ /**
+ * Get the farthest date from the instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2
+ *
+ * @return static
+ */
+ public function farthest($date1, $date2)
+ {
+ return $this->floatDiffInSeconds($date1) > $this->floatDiffInSeconds($date2) ? $date1 : $date2;
+ }
+
+ /**
+ * Get the minimum instance between a given instance (default now) and the current instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ *
+ * @return static
+ */
+ public function min($date = null)
+ {
+ $date = $this->resolveCarbon($date);
+
+ return $this->lt($date) ? $this : $date;
+ }
+
+ /**
+ * Get the minimum instance between a given instance (default now) and the current instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see min()
+ *
+ * @return static
+ */
+ public function minimum($date = null)
+ {
+ return $this->min($date);
+ }
+
+ /**
+ * Get the maximum instance between a given instance (default now) and the current instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ *
+ * @return static
+ */
+ public function max($date = null)
+ {
+ $date = $this->resolveCarbon($date);
+
+ return $this->gt($date) ? $this : $date;
+ }
+
+ /**
+ * Get the maximum instance between a given instance (default now) and the current instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|mixed $date
+ *
+ * @see max()
+ *
+ * @return static
+ */
+ public function maximum($date = null)
+ {
+ return $this->max($date);
+ }
+
+ /**
+ * Determines if the instance is a weekday.
+ *
+ * @return bool
+ */
+ public function isWeekday()
+ {
+ return !$this->isWeekend();
+ }
+
+ /**
+ * Determines if the instance is a weekend day.
+ *
+ * @return bool
+ */
+ public function isWeekend()
+ {
+ return in_array($this->dayOfWeek, static::$weekendDays);
+ }
+
+ /**
+ * Determines if the instance is yesterday.
+ *
+ * @return bool
+ */
+ public function isYesterday()
+ {
+ return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString();
+ }
+
+ /**
+ * Determines if the instance is today.
+ *
+ * @return bool
+ */
+ public function isToday()
+ {
+ return $this->toDateString() === $this->nowWithSameTz()->toDateString();
+ }
+
+ /**
+ * Determines if the instance is tomorrow.
+ *
+ * @return bool
+ */
+ public function isTomorrow()
+ {
+ return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString();
+ }
+
+ /**
+ * Determines if the instance is within the next week.
+ *
+ * @return bool
+ */
+ public function isNextWeek()
+ {
+ return $this->weekOfYear === $this->nowWithSameTz()->addWeek()->weekOfYear;
+ }
+
+ /**
+ * Determines if the instance is within the last week.
+ *
+ * @return bool
+ */
+ public function isLastWeek()
+ {
+ return $this->weekOfYear === $this->nowWithSameTz()->subWeek()->weekOfYear;
+ }
+
+ /**
+ * Determines if the instance is within the next quarter.
+ *
+ * @return bool
+ */
+ public function isNextQuarter()
+ {
+ return $this->quarter === $this->nowWithSameTz()->addQuarter()->quarter;
+ }
+
+ /**
+ * Determines if the instance is within the last quarter.
+ *
+ * @return bool
+ */
+ public function isLastQuarter()
+ {
+ return $this->quarter === $this->nowWithSameTz()->subQuarter()->quarter;
+ }
+
+ /**
+ * Determines if the instance is within the next month.
+ *
+ * @return bool
+ */
+ public function isNextMonth()
+ {
+ return $this->month === $this->nowWithSameTz()->addMonthNoOverflow()->month;
+ }
+
+ /**
+ * Determines if the instance is within the last month.
+ *
+ * @return bool
+ */
+ public function isLastMonth()
+ {
+ return $this->month === $this->nowWithSameTz()->subMonthNoOverflow()->month;
+ }
+
+ /**
+ * Determines if the instance is within next year.
+ *
+ * @return bool
+ */
+ public function isNextYear()
+ {
+ return $this->year === $this->nowWithSameTz()->addYear()->year;
+ }
+
+ /**
+ * Determines if the instance is within the previous year.
+ *
+ * @return bool
+ */
+ public function isLastYear()
+ {
+ return $this->year === $this->nowWithSameTz()->subYear()->year;
+ }
+
+ /**
+ * Determines if the instance is in the future, ie. greater (after) than now.
+ *
+ * @return bool
+ */
+ public function isFuture()
+ {
+ return $this->gt($this->nowWithSameTz());
+ }
+
+ /**
+ * Determines if the instance is in the past, ie. less (before) than now.
+ *
+ * @return bool
+ */
+ public function isPast()
+ {
+ return $this->lt($this->nowWithSameTz());
+ }
+
+ /**
+ * Determines if the instance is a leap year.
+ *
+ * @return bool
+ */
+ public function isLeapYear()
+ {
+ return $this->format('L') === '1';
+ }
+
+ /**
+ * Determines if the instance is a long year
+ *
+ * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates
+ *
+ * @return bool
+ */
+ public function isLongYear()
+ {
+ return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53;
+ }
+
+ /**
+ * Compares the formatted values of the two dates.
+ *
+ * @param string $format The date formats to compare.
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return bool
+ */
+ public function isSameAs($format, $date = null)
+ {
+ $date = $date ?: static::now($this->tz);
+
+ static::expectDateTime($date, 'null');
+
+ return $this->format($format) === $date->format($format);
+ }
+
+ /**
+ * Determines if the instance is in the current year.
+ *
+ * @return bool
+ */
+ public function isCurrentYear()
+ {
+ return $this->isSameYear();
+ }
+
+ /**
+ * Checks if the passed in date is in the same year as the instance year.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day.
+ *
+ * @return bool
+ */
+ public function isSameYear($date = null)
+ {
+ return $this->isSameAs('Y', $date);
+ }
+
+ /**
+ * Determines if the instance is in the current month.
+ *
+ * @return bool
+ */
+ public function isCurrentQuarter()
+ {
+ return $this->isSameQuarter();
+ }
+
+ /**
+ * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed).
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day.
+ * @param bool $ofSameYear Check if it is the same month in the same year.
+ *
+ * @return bool
+ */
+ public function isSameQuarter($date = null, $ofSameYear = null)
+ {
+ $date = $date ? static::instance($date) : static::now($this->tz);
+
+ static::expectDateTime($date, 'null');
+
+ $ofSameYear = is_null($ofSameYear) ? static::shouldCompareYearWithMonth() : $ofSameYear;
+
+ return $this->quarter === $date->quarter && (!$ofSameYear || $this->isSameYear($date));
+ }
+
+ /**
+ * Determines if the instance is in the current month.
+ *
+ * @param bool $ofSameYear Check if it is the same month in the same year.
+ *
+ * @return bool
+ */
+ public function isCurrentMonth($ofSameYear = null)
+ {
+ return $this->isSameMonth(null, $ofSameYear);
+ }
+
+ /**
+ * Checks if the passed in date is in the same month as the instance´s month.
+ *
+ * Note that this defaults to only comparing the month while ignoring the year.
+ * To test if it is the same exact month of the same year, pass in true as the second parameter.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date.
+ * @param bool $ofSameYear Check if it is the same month in the same year.
+ *
+ * @return bool
+ */
+ public function isSameMonth($date = null, $ofSameYear = null)
+ {
+ $ofSameYear = is_null($ofSameYear) ? static::shouldCompareYearWithMonth() : $ofSameYear;
+
+ return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date);
+ }
+
+ /**
+ * Determines if the instance is in the current day.
+ *
+ * @return bool
+ */
+ public function isCurrentDay()
+ {
+ return $this->isSameDay();
+ }
+
+ /**
+ * Checks if the passed in date is the same exact day as the instance´s day.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date.
+ *
+ * @return bool
+ */
+ public function isSameDay($date = null)
+ {
+ return $this->isSameAs('Y-m-d', $date);
+ }
+
+ /**
+ * Determines if the instance is in the current hour.
+ *
+ * @return bool
+ */
+ public function isCurrentHour()
+ {
+ return $this->isSameHour();
+ }
+
+ /**
+ * Checks if the passed in date is the same exact hour as the instance´s hour.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date.
+ *
+ * @return bool
+ */
+ public function isSameHour($date = null)
+ {
+ return $this->isSameAs('Y-m-d H', $date);
+ }
+
+ /**
+ * Determines if the instance is in the current minute.
+ *
+ * @return bool
+ */
+ public function isCurrentMinute()
+ {
+ return $this->isSameMinute();
+ }
+
+ /**
+ * Checks if the passed in date is the same exact minute as the instance´s minute.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date.
+ *
+ * @return bool
+ */
+ public function isSameMinute($date = null)
+ {
+ return $this->isSameAs('Y-m-d H:i', $date);
+ }
+
+ /**
+ * Determines if the instance is in the current second.
+ *
+ * @return bool
+ */
+ public function isCurrentSecond()
+ {
+ return $this->isSameSecond();
+ }
+
+ /**
+ * Checks if the passed in date is the same exact second as the instance´s second.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date.
+ *
+ * @return bool
+ */
+ public function isSameSecond($date = null)
+ {
+ return $this->isSameAs('Y-m-d H:i:s', $date);
+ }
+
+ /**
+ * Checks if this day is a specific day of the week.
+ *
+ * @param int $dayOfWeek
+ *
+ * @return bool
+ */
+ public function isDayOfWeek($dayOfWeek)
+ {
+ return $this->dayOfWeek === $dayOfWeek;
+ }
+
+ /**
+ * Checks if this day is a Sunday.
+ *
+ * @return bool
+ */
+ public function isSunday()
+ {
+ return $this->dayOfWeek === static::SUNDAY;
+ }
+
+ /**
+ * Checks if this day is a Monday.
+ *
+ * @return bool
+ */
+ public function isMonday()
+ {
+ return $this->dayOfWeek === static::MONDAY;
+ }
+
+ /**
+ * Checks if this day is a Tuesday.
+ *
+ * @return bool
+ */
+ public function isTuesday()
+ {
+ return $this->dayOfWeek === static::TUESDAY;
+ }
+
+ /**
+ * Checks if this day is a Wednesday.
+ *
+ * @return bool
+ */
+ public function isWednesday()
+ {
+ return $this->dayOfWeek === static::WEDNESDAY;
+ }
+
+ /**
+ * Checks if this day is a Thursday.
+ *
+ * @return bool
+ */
+ public function isThursday()
+ {
+ return $this->dayOfWeek === static::THURSDAY;
+ }
+
+ /**
+ * Checks if this day is a Friday.
+ *
+ * @return bool
+ */
+ public function isFriday()
+ {
+ return $this->dayOfWeek === static::FRIDAY;
+ }
+
+ /**
+ * Checks if this day is a Saturday.
+ *
+ * @return bool
+ */
+ public function isSaturday()
+ {
+ return $this->dayOfWeek === static::SATURDAY;
+ }
+
+ /**
+ * Check if its the birthday. Compares the date/month values of the two dates.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day.
+ *
+ * @return bool
+ */
+ public function isBirthday($date = null)
+ {
+ return $this->isSameAs('md', $date);
+ }
+
+ /**
+ * Check if today is the last day of the Month
+ *
+ * @return bool
+ */
+ public function isLastOfMonth()
+ {
+ return $this->day === $this->daysInMonth;
+ }
+
+ /**
+ * Check if the instance is start of day / midnight.
+ *
+ * @param bool $checkMicroseconds check time at microseconds precision
+ * /!\ Warning, this is not reliable with PHP < 7.1.4
+ *
+ * @return bool
+ */
+ public function isStartOfDay($checkMicroseconds = false)
+ {
+ return $checkMicroseconds
+ ? $this->format('H:i:s.u') === '00:00:00.000000'
+ : $this->format('H:i:s') === '00:00:00';
+ }
+
+ /**
+ * Check if the instance is end of day.
+ *
+ * @param bool $checkMicroseconds check time at microseconds precision
+ * /!\ Warning, this is not reliable with PHP < 7.1.4
+ *
+ * @return bool
+ */
+ public function isEndOfDay($checkMicroseconds = false)
+ {
+ return $checkMicroseconds
+ ? $this->format('H:i:s.u') === '23:59:59.999999'
+ : $this->format('H:i:s') === '23:59:59';
+ }
+
+ /**
+ * Check if the instance is start of day / midnight.
+ *
+ * @return bool
+ */
+ public function isMidnight()
+ {
+ return $this->isStartOfDay();
+ }
+
+ /**
+ * Check if the instance is midday.
+ *
+ * @return bool
+ */
+ public function isMidday()
+ {
+ return $this->format('G:i:s') === static::$midDayAt.':00:00';
+ }
+
+ /**
+ * Checks if the (date)time string is in a given format.
+ *
+ * @param string $date
+ * @param string $format
+ *
+ * @return bool
+ */
+ public static function hasFormat($date, $format)
+ {
+ try {
+ // Try to create a DateTime object. Throws an InvalidArgumentException if the provided time string
+ // doesn't match the format in any way.
+ static::createFromFormat($format, $date);
+
+ // createFromFormat() is known to handle edge cases silently.
+ // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format.
+ // To ensure we're really testing against our desired format, perform an additional regex validation.
+ $regex = strtr(
+ preg_quote($format, '/'),
+ static::$regexFormats
+ );
+
+ return (bool) preg_match('/^'.$regex.'$/', $date);
+ } catch (InvalidArgumentException $e) {
+ }
+
+ return false;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /////////////////// ADDITIONS AND SUBTRACTIONS ////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Add centuries to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addCenturies($value)
+ {
+ return $this->addYears(static::YEARS_PER_CENTURY * $value);
+ }
+
+ /**
+ * Add a century to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addCentury($value = 1)
+ {
+ return $this->addCenturies($value);
+ }
+
+ /**
+ * Remove centuries from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subCenturies($value)
+ {
+ return $this->addCenturies(-1 * $value);
+ }
+
+ /**
+ * Remove a century from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subCentury($value = 1)
+ {
+ return $this->subCenturies($value);
+ }
+
+ /**
+ * Add years to the instance. Positive $value travel forward while
+ * negative $value travel into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addYears($value)
+ {
+ if ($this->shouldOverflowYears()) {
+ return $this->addYearsWithOverflow($value);
+ }
+
+ return $this->addYearsNoOverflow($value);
+ }
+
+ /**
+ * Add a year to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addYear($value = 1)
+ {
+ return $this->addYears($value);
+ }
+
+ /**
+ * Add years to the instance with no overflow of months
+ * Positive $value travel forward while
+ * negative $value travel into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addYearsNoOverflow($value)
+ {
+ return $this->addMonthsNoOverflow($value * static::MONTHS_PER_YEAR);
+ }
+
+ /**
+ * Add year with overflow months set to false
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addYearNoOverflow($value = 1)
+ {
+ return $this->addYearsNoOverflow($value);
+ }
+
+ /**
+ * Add years to the instance.
+ * Positive $value travel forward while
+ * negative $value travel into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addYearsWithOverflow($value)
+ {
+ return $this->modify((int) $value.' year');
+ }
+
+ /**
+ * Add year with overflow.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addYearWithOverflow($value = 1)
+ {
+ return $this->addYearsWithOverflow($value);
+ }
+
+ /**
+ * Remove years from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subYears($value)
+ {
+ return $this->addYears(-1 * $value);
+ }
+
+ /**
+ * Remove a year from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subYear($value = 1)
+ {
+ return $this->subYears($value);
+ }
+
+ /**
+ * Remove years from the instance with no month overflow.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subYearsNoOverflow($value)
+ {
+ return $this->subMonthsNoOverflow($value * static::MONTHS_PER_YEAR);
+ }
+
+ /**
+ * Remove year from the instance with no month overflow
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subYearNoOverflow($value = 1)
+ {
+ return $this->subYearsNoOverflow($value);
+ }
+
+ /**
+ * Remove years from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subYearsWithOverflow($value)
+ {
+ return $this->subMonthsWithOverflow($value * static::MONTHS_PER_YEAR);
+ }
+
+ /**
+ * Remove year from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subYearWithOverflow($value = 1)
+ {
+ return $this->subYearsWithOverflow($value);
+ }
+
+ /**
+ * Add quarters to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addQuarters($value)
+ {
+ return $this->addMonths(static::MONTHS_PER_QUARTER * $value);
+ }
+
+ /**
+ * Add a quarter to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addQuarter($value = 1)
+ {
+ return $this->addQuarters($value);
+ }
+
+ /**
+ * Remove quarters from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subQuarters($value)
+ {
+ return $this->addQuarters(-1 * $value);
+ }
+
+ /**
+ * Remove a quarter from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subQuarter($value = 1)
+ {
+ return $this->subQuarters($value);
+ }
+
+ /**
+ * Add months to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addMonths($value)
+ {
+ if (static::shouldOverflowMonths()) {
+ return $this->addMonthsWithOverflow($value);
+ }
+
+ return $this->addMonthsNoOverflow($value);
+ }
+
+ /**
+ * Add a month to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addMonth($value = 1)
+ {
+ return $this->addMonths($value);
+ }
+
+ /**
+ * Remove months from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subMonths($value)
+ {
+ return $this->addMonths(-1 * $value);
+ }
+
+ /**
+ * Remove a month from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subMonth($value = 1)
+ {
+ return $this->subMonths($value);
+ }
+
+ /**
+ * Add months to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addMonthsWithOverflow($value)
+ {
+ return $this->modify((int) $value.' month');
+ }
+
+ /**
+ * Add a month to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addMonthWithOverflow($value = 1)
+ {
+ return $this->addMonthsWithOverflow($value);
+ }
+
+ /**
+ * Remove months from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subMonthsWithOverflow($value)
+ {
+ return $this->addMonthsWithOverflow(-1 * $value);
+ }
+
+ /**
+ * Remove a month from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subMonthWithOverflow($value = 1)
+ {
+ return $this->subMonthsWithOverflow($value);
+ }
+
+ /**
+ * Add months without overflowing to the instance. Positive $value
+ * travels forward while negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addMonthsNoOverflow($value)
+ {
+ $day = $this->day;
+
+ $this->modify((int) $value.' month');
+
+ if ($day !== $this->day) {
+ $this->modify('last day of previous month');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a month with no overflow to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addMonthNoOverflow($value = 1)
+ {
+ return $this->addMonthsNoOverflow($value);
+ }
+
+ /**
+ * Remove months with no overflow from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subMonthsNoOverflow($value)
+ {
+ return $this->addMonthsNoOverflow(-1 * $value);
+ }
+
+ /**
+ * Remove a month with no overflow from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subMonthNoOverflow($value = 1)
+ {
+ return $this->subMonthsNoOverflow($value);
+ }
+
+ /**
+ * Add days to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addDays($value)
+ {
+ return $this->modify((int) $value.' day');
+ }
+
+ /**
+ * Add a day to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addDay($value = 1)
+ {
+ return $this->addDays($value);
+ }
+
+ /**
+ * Remove days from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subDays($value)
+ {
+ return $this->addDays(-1 * $value);
+ }
+
+ /**
+ * Remove a day from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subDay($value = 1)
+ {
+ return $this->subDays($value);
+ }
+
+ /**
+ * Add weekdays to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addWeekdays($value)
+ {
+ // Fix for weekday bug https://bugs.php.net/bug.php?id=54909
+ $t = $this->toTimeString();
+ $this->modify((int) $value.' weekday');
+
+ return $this->setTimeFromTimeString($t);
+ }
+
+ /**
+ * Add a weekday to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addWeekday($value = 1)
+ {
+ return $this->addWeekdays($value);
+ }
+
+ /**
+ * Remove weekdays from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subWeekdays($value)
+ {
+ return $this->addWeekdays(-1 * $value);
+ }
+
+ /**
+ * Remove a weekday from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subWeekday($value = 1)
+ {
+ return $this->subWeekdays($value);
+ }
+
+ /**
+ * Add weeks to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addWeeks($value)
+ {
+ return $this->modify((int) $value.' week');
+ }
+
+ /**
+ * Add a week to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addWeek($value = 1)
+ {
+ return $this->addWeeks($value);
+ }
+
+ /**
+ * Remove weeks to the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subWeeks($value)
+ {
+ return $this->addWeeks(-1 * $value);
+ }
+
+ /**
+ * Remove a week from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subWeek($value = 1)
+ {
+ return $this->subWeeks($value);
+ }
+
+ /**
+ * Add hours to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addHours($value)
+ {
+ return $this->modify((int) $value.' hour');
+ }
+
+ /**
+ * Add hours to the instance using timestamp. Positive $value travels
+ * forward while negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addRealHours($value)
+ {
+ return $this->addRealMinutes($value * static::MINUTES_PER_HOUR);
+ }
+
+ /**
+ * Add an hour to the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addHour($value = 1)
+ {
+ return $this->addHours($value);
+ }
+
+ /**
+ * Add an hour to the instance using timestamp.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addRealHour($value = 1)
+ {
+ return $this->addRealHours($value);
+ }
+
+ /**
+ * Remove hours from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subHours($value)
+ {
+ return $this->addHours(-1 * $value);
+ }
+
+ /**
+ * Remove hours from the instance using timestamp.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subRealHours($value)
+ {
+ return $this->addRealHours(-1 * $value);
+ }
+
+ /**
+ * Remove an hour from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subHour($value = 1)
+ {
+ return $this->subHours($value);
+ }
+
+ /**
+ * Remove an hour from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subRealHour($value = 1)
+ {
+ return $this->subRealHours($value);
+ }
+
+ /**
+ * Add minutes to the instance using timestamp. Positive $value
+ * travels forward while negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addMinutes($value)
+ {
+ return $this->modify((int) $value.' minute');
+ }
+
+ /**
+ * Add minutes to the instance using timestamp. Positive $value travels
+ * forward while negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addRealMinutes($value)
+ {
+ return $this->addRealSeconds($value * static::SECONDS_PER_MINUTE);
+ }
+
+ /**
+ * Add a minute to the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addMinute($value = 1)
+ {
+ return $this->addMinutes($value);
+ }
+
+ /**
+ * Add a minute to the instance using timestamp.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addRealMinute($value = 1)
+ {
+ return $this->addRealMinutes($value);
+ }
+
+ /**
+ * Remove a minute from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subMinute($value = 1)
+ {
+ return $this->subMinutes($value);
+ }
+
+ /**
+ * Remove a minute from the instance using timestamp.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subRealMinute($value = 1)
+ {
+ return $this->addRealMinutes(-1 * $value);
+ }
+
+ /**
+ * Remove minutes from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subMinutes($value)
+ {
+ return $this->addMinutes(-1 * $value);
+ }
+
+ /**
+ * Remove a minute from the instance using timestamp.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subRealMinutes($value = 1)
+ {
+ return $this->subRealMinute($value);
+ }
+
+ /**
+ * Add seconds to the instance. Positive $value travels forward while
+ * negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addSeconds($value)
+ {
+ return $this->modify((int) $value.' second');
+ }
+
+ /**
+ * Add seconds to the instance using timestamp. Positive $value travels
+ * forward while negative $value travels into the past.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addRealSeconds($value)
+ {
+ return $this->setTimestamp($this->getTimestamp() + $value);
+ }
+
+ /**
+ * Add a second to the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addSecond($value = 1)
+ {
+ return $this->addSeconds($value);
+ }
+
+ /**
+ * Add a second to the instance using timestamp.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function addRealSecond($value = 1)
+ {
+ return $this->addRealSeconds($value);
+ }
+
+ /**
+ * Remove seconds from the instance.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subSeconds($value)
+ {
+ return $this->addSeconds(-1 * $value);
+ }
+
+ /**
+ * Remove seconds from the instance using timestamp.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subRealSeconds($value)
+ {
+ return $this->addRealSeconds(-1 * $value);
+ }
+
+ /**
+ * Remove a second from the instance
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subSecond($value = 1)
+ {
+ return $this->subSeconds($value);
+ }
+
+ /**
+ * Remove a second from the instance using timestamp.
+ *
+ * @param int $value
+ *
+ * @return static
+ */
+ public function subRealSecond($value = 1)
+ {
+ return $this->subRealSeconds($value);
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /////////////////////////// DIFFERENCES ///////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * @param DateInterval $diff
+ * @param bool $absolute
+ * @param bool $trimMicroseconds
+ *
+ * @return CarbonInterval
+ */
+ protected static function fixDiffInterval(DateInterval $diff, $absolute, $trimMicroseconds)
+ {
+ $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->h += 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);
+ }
+
+ /**
+ * Get the difference in years
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInYears($date = null, $absolute = true)
+ {
+ return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%y');
+ }
+
+ /**
+ * Get the difference in months
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInMonths($date = null, $absolute = true)
+ {
+ $date = $this->resolveCarbon($date);
+
+ return $this->diffInYears($date, $absolute) * static::MONTHS_PER_YEAR + (int) $this->diff($date, $absolute)->format('%r%m');
+ }
+
+ /**
+ * Get the difference in weeks
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInWeeks($date = null, $absolute = true)
+ {
+ return (int) ($this->diffInDays($date, $absolute) / static::DAYS_PER_WEEK);
+ }
+
+ /**
+ * Get the difference in days
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInDays($date = null, $absolute = true)
+ {
+ return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%a');
+ }
+
+ /**
+ * Get the difference in days using a filter closure
+ *
+ * @param Closure $callback
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true)
+ {
+ return $this->diffFiltered(CarbonInterval::day(), $callback, $date, $absolute);
+ }
+
+ /**
+ * Get the difference in hours using a filter closure
+ *
+ * @param Closure $callback
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true)
+ {
+ return $this->diffFiltered(CarbonInterval::hour(), $callback, $date, $absolute);
+ }
+
+ /**
+ * Get the difference by the given interval using a filter closure
+ *
+ * @param CarbonInterval $ci An interval to traverse by
+ * @param Closure $callback
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true)
+ {
+ $start = $this;
+ $end = $this->resolveCarbon($date);
+ $inverse = false;
+
+ if ($end < $start) {
+ $start = $end;
+ $end = $this;
+ $inverse = true;
+ }
+
+ $period = new DatePeriod($start, $ci, $end);
+ $values = array_filter(iterator_to_array($period), function ($date) use ($callback) {
+ return call_user_func($callback, Carbon::instance($date));
+ });
+
+ $diff = count($values);
+
+ return $inverse && !$absolute ? -$diff : $diff;
+ }
+
+ /**
+ * Get the difference in weekdays
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInWeekdays($date = null, $absolute = true)
+ {
+ return $this->diffInDaysFiltered(function (Carbon $date) {
+ return $date->isWeekday();
+ }, $date, $absolute);
+ }
+
+ /**
+ * Get the difference in weekend days using a filter
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInWeekendDays($date = null, $absolute = true)
+ {
+ return $this->diffInDaysFiltered(function (Carbon $date) {
+ return $date->isWeekend();
+ }, $date, $absolute);
+ }
+
+ /**
+ * Get the difference in hours.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInHours($date = null, $absolute = true)
+ {
+ return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR);
+ }
+
+ /**
+ * Get the difference in hours using timestamps.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInRealHours($date = null, $absolute = true)
+ {
+ return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR);
+ }
+
+ /**
+ * Get the difference in minutes.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInMinutes($date = null, $absolute = true)
+ {
+ return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE);
+ }
+
+ /**
+ * Get the difference in minutes using timestamps.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInRealMinutes($date = null, $absolute = true)
+ {
+ return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE);
+ }
+
+ /**
+ * Get the difference in seconds.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ 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 +
+ $diff->s;
+
+ return $absolute || !$diff->invert ? $value : -$value;
+ }
+
+ /**
+ * Get the difference in seconds using timestamps.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInRealSeconds($date = null, $absolute = true)
+ {
+ $date = $this->resolveCarbon($date);
+ $value = $date->getTimestamp() - $this->getTimestamp();
+
+ return $absolute ? abs($value) : $value;
+ }
+
+ /**
+ * Get the difference in milliseconds.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInMilliseconds($date = null, $absolute = true)
+ {
+ return (int) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND);
+ }
+
+ /**
+ * Get the difference in milliseconds using timestamps.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInRealMilliseconds($date = null, $absolute = true)
+ {
+ return (int) ($this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND);
+ }
+
+ /**
+ * Get the difference in microseconds.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInMicroseconds($date = null, $absolute = true)
+ {
+ $diff = $this->diff($this->resolveCarbon($date));
+ $micro = isset($diff->f) ? $diff->f : 0;
+ $value = (int) round((((($diff->days * static::HOURS_PER_DAY) +
+ $diff->h) * static::MINUTES_PER_HOUR +
+ $diff->i) * static::SECONDS_PER_MINUTE +
+ ($micro + $diff->s)) * static::MICROSECONDS_PER_SECOND);
+
+ return $absolute || !$diff->invert ? $value : -$value;
+ }
+
+ /**
+ * Get the difference in microseconds using timestamps.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ * @param bool $absolute Get the absolute of the difference
+ *
+ * @return int
+ */
+ public function diffInRealMicroseconds($date = null, $absolute = true)
+ {
+ /** @var Carbon $date */
+ $date = $this->resolveCarbon($date);
+ $value = ($date->timestamp - $this->timestamp) * static::MICROSECONDS_PER_SECOND +
+ $date->micro - $this->micro;
+
+ return $absolute ? abs($value) : $value;
+ }
+
+ /**
+ * The number of seconds since midnight.
+ *
+ * @return int
+ */
+ public function secondsSinceMidnight()
+ {
+ return $this->diffInSeconds($this->copy()->startOfDay());
+ }
+
+ /**
+ * The number of seconds until 23:59:59.
+ *
+ * @return int
+ */
+ public function secondsUntilEndOfDay()
+ {
+ return $this->diffInSeconds($this->copy()->endOfDay());
+ }
+
+ /**
+ * Get the difference in a human readable format in the current locale.
+ *
+ * When comparing a value in the past to default now:
+ * 1 hour ago
+ * 5 months ago
+ *
+ * When comparing a value in the future to default now:
+ * 1 hour from now
+ * 5 months from now
+ *
+ * When comparing a value in the past to another value:
+ * 1 hour before
+ * 5 months before
+ *
+ * When comparing a value in the future to another value:
+ * 1 hour after
+ * 5 months after
+ *
+ * @param Carbon|null $other
+ * @param bool $absolute removes time difference modifiers ago, after, etc
+ * @param bool $short displays short format of time units
+ * @param int $parts displays number of parts in the interval
+ *
+ * @return string
+ */
+ public function diffForHumans($other = null, $absolute = false, $short = false, $parts = 1)
+ {
+ $isNow = $other === null;
+ $relativeToNow = $isNow;
+
+ if ($absolute === static::DIFF_RELATIVE_TO_NOW) {
+ $absolute = false;
+ $relativeToNow = true;
+ } elseif ($absolute === static::DIFF_RELATIVE_TO_OTHER) {
+ $absolute = false;
+ $relativeToNow = false;
+ }
+
+ $interval = array();
+
+ $parts = min(6, max(1, (int) $parts));
+ $count = 1;
+ $unit = $short ? 's' : 'second';
+
+ if ($isNow) {
+ $other = $this->nowWithSameTz();
+ } elseif (!$other instanceof DateTime && !$other instanceof DateTimeInterface) {
+ $other = static::parse($other);
+ }
+
+ $diffInterval = $this->diff($other);
+
+ $diffIntervalArray = array(
+ array('value' => $diffInterval->y, 'unit' => 'year', 'unitShort' => 'y'),
+ array('value' => $diffInterval->m, 'unit' => 'month', 'unitShort' => 'm'),
+ array('value' => $diffInterval->d, 'unit' => 'day', 'unitShort' => 'd'),
+ array('value' => $diffInterval->h, 'unit' => 'hour', 'unitShort' => 'h'),
+ array('value' => $diffInterval->i, 'unit' => 'minute', 'unitShort' => 'min'),
+ array('value' => $diffInterval->s, 'unit' => 'second', 'unitShort' => 's'),
+ );
+
+ foreach ($diffIntervalArray as $diffIntervalData) {
+ if ($diffIntervalData['value'] > 0) {
+ $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit'];
+ $count = $diffIntervalData['value'];
+
+ if ($diffIntervalData['unit'] === 'day' && $count >= static::DAYS_PER_WEEK) {
+ $unit = $short ? 'w' : 'week';
+ $count = (int) ($count / static::DAYS_PER_WEEK);
+
+ $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count));
+
+ // get the count days excluding weeks (might be zero)
+ $numOfDaysCount = (int) ($diffIntervalData['value'] - ($count * static::DAYS_PER_WEEK));
+
+ if ($numOfDaysCount > 0 && count($interval) < $parts) {
+ $unit = $short ? 'd' : 'day';
+ $count = $numOfDaysCount;
+ $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count));
+ }
+ } else {
+ $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count));
+ }
+ }
+
+ // break the loop after we get the required number of parts in array
+ if (count($interval) >= $parts) {
+ break;
+ }
+ }
+
+ if (count($interval) === 0) {
+ if ($isNow && static::getHumanDiffOptions() & self::JUST_NOW) {
+ $key = 'diff_now';
+ $translation = static::translator()->trans($key);
+ if ($translation !== $key) {
+ return $translation;
+ }
+ }
+ $count = static::getHumanDiffOptions() & self::NO_ZERO_DIFF ? 1 : 0;
+ $unit = $short ? 's' : 'second';
+ $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count));
+ }
+
+ // join the interval parts by a space
+ $time = implode(' ', $interval);
+
+ unset($diffIntervalArray, $interval);
+
+ if ($absolute) {
+ return $time;
+ }
+
+ $isFuture = $diffInterval->invert === 1;
+
+ $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before');
+
+ if ($parts === 1) {
+ if ($isNow && $unit === 'day') {
+ if ($count === 1 && static::getHumanDiffOptions() & self::ONE_DAY_WORDS) {
+ $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday';
+ $translation = static::translator()->trans($key);
+ if ($translation !== $key) {
+ return $translation;
+ }
+ }
+ if ($count === 2 && static::getHumanDiffOptions() & self::TWO_DAY_WORDS) {
+ $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday';
+ $translation = static::translator()->trans($key);
+ if ($translation !== $key) {
+ return $translation;
+ }
+ }
+ }
+ // Some languages have special pluralization for past and future tense.
+ $key = $unit.'_'.$transId;
+ if ($key !== static::translator()->transChoice($key, $count)) {
+ $time = static::translator()->transChoice($key, $count, array(':count' => $count));
+ }
+ }
+
+ return static::translator()->trans($transId, array(':time' => $time));
+ }
+
+ /**
+ * @alias diffForHumans
+ *
+ * Get the difference in a human readable format in the current locale.
+ *
+ * When comparing a value in the past to default now:
+ * 1 hour ago
+ * 5 months ago
+ *
+ * When comparing a value in the future to default now:
+ * 1 hour from now
+ * 5 months from now
+ *
+ * When comparing a value in the past to another value:
+ * 1 hour before
+ * 5 months before
+ *
+ * When comparing a value in the future to another value:
+ * 1 hour after
+ * 5 months after
+ *
+ * @param Carbon|null $other
+ * @param bool $absolute removes time difference modifiers ago, after, etc
+ * @param bool $short displays short format of time units
+ * @param int $parts displays number of parts in the interval
+ *
+ * @return string
+ */
+ public function from($other = null, $absolute = false, $short = false, $parts = 1)
+ {
+ if (!$other && !$absolute) {
+ $absolute = static::DIFF_RELATIVE_TO_NOW;
+ }
+
+ return $this->diffForHumans($other, $absolute, $short, $parts);
+ }
+
+ /**
+ * @alias diffForHumans
+ *
+ * Get the difference in a human readable format in the current locale.
+ *
+ * When comparing a value in the past to default now:
+ * 1 hour ago
+ * 5 months ago
+ *
+ * When comparing a value in the future to default now:
+ * 1 hour from now
+ * 5 months from now
+ *
+ * When comparing a value in the past to another value:
+ * 1 hour before
+ * 5 months before
+ *
+ * When comparing a value in the future to another value:
+ * 1 hour after
+ * 5 months after
+ *
+ * @param Carbon|null $other
+ * @param bool $absolute removes time difference modifiers ago, after, etc
+ * @param bool $short displays short format of time units
+ * @param int $parts displays number of parts in the interval
+ *
+ * @return string
+ */
+ public function since($other = null, $absolute = false, $short = false, $parts = 1)
+ {
+ return $this->diffForHumans($other, $absolute, $short, $parts);
+ }
+
+ /**
+ * Get the difference in a human readable format in the current locale from an other
+ * instance given (or now if null given) to current instance.
+ *
+ * When comparing a value in the past to default now:
+ * 1 hour from now
+ * 5 months from now
+ *
+ * When comparing a value in the future to default now:
+ * 1 hour ago
+ * 5 months ago
+ *
+ * When comparing a value in the past to another value:
+ * 1 hour after
+ * 5 months after
+ *
+ * When comparing a value in the future to another value:
+ * 1 hour before
+ * 5 months before
+ *
+ * @param Carbon|null $other
+ * @param bool $absolute removes time difference modifiers ago, after, etc
+ * @param bool $short displays short format of time units
+ * @param int $parts displays number of parts in the interval
+ *
+ * @return string
+ */
+ public function to($other = null, $absolute = false, $short = false, $parts = 1)
+ {
+ if (!$other && !$absolute) {
+ $absolute = static::DIFF_RELATIVE_TO_NOW;
+ }
+
+ return $this->resolveCarbon($other)->diffForHumans($this, $absolute, $short, $parts);
+ }
+
+ /**
+ * @alias to
+ *
+ * Get the difference in a human readable format in the current locale from an other
+ * instance given (or now if null given) to current instance.
+ *
+ * @param Carbon|null $other
+ * @param bool $absolute removes time difference modifiers ago, after, etc
+ * @param bool $short displays short format of time units
+ * @param int $parts displays number of parts in the interval
+ *
+ * @return string
+ */
+ public function until($other = null, $absolute = false, $short = false, $parts = 1)
+ {
+ return $this->to($other, $absolute, $short, $parts);
+ }
+
+ /**
+ * Get the difference in a human readable format in the current locale from current
+ * instance to now.
+ *
+ * @param bool $absolute removes time difference modifiers ago, after, etc
+ * @param bool $short displays short format of time units
+ * @param int $parts displays number of parts in the interval
+ *
+ * @return string
+ */
+ public function fromNow($absolute = null, $short = false, $parts = 1)
+ {
+ $other = null;
+
+ if ($absolute instanceof DateTimeInterface) {
+ list($other, $absolute, $short, $parts) = array_pad(func_get_args(), 5, null);
+ }
+
+ return $this->from($other, $absolute, $short, $parts);
+ }
+
+ /**
+ * Get the difference in a human readable format in the current locale from an other
+ * instance given to now
+ *
+ * @param bool $absolute removes time difference modifiers ago, after, etc
+ * @param bool $short displays short format of time units
+ * @param int $parts displays number of parts in the interval
+ *
+ * @return string
+ */
+ public function toNow($absolute = null, $short = false, $parts = 1)
+ {
+ return $this->to(null, $absolute, $short, $parts);
+ }
+
+ /**
+ * Get the difference in a human readable format in the current locale from an other
+ * instance given to now
+ *
+ * @param bool $absolute removes time difference modifiers ago, after, etc
+ * @param bool $short displays short format of time units
+ * @param int $parts displays number of parts in the interval
+ *
+ * @return string
+ */
+ public function ago($absolute = null, $short = false, $parts = 1)
+ {
+ $other = null;
+
+ if ($absolute instanceof DateTimeInterface) {
+ list($other, $absolute, $short, $parts) = array_pad(func_get_args(), 5, null);
+ }
+
+ return $this->from($other, $absolute, $short, $parts);
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ //////////////////////////// MODIFIERS ////////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Resets the time to 00:00:00 start of day
+ *
+ * @return static
+ */
+ public function startOfDay()
+ {
+ return $this->modify('00:00:00.000000');
+ }
+
+ /**
+ * Resets the time to 23:59:59 end of day
+ *
+ * @return static
+ */
+ public function endOfDay()
+ {
+ return $this->modify('23:59:59.999999');
+ }
+
+ /**
+ * Resets the date to the first day of the month and the time to 00:00:00
+ *
+ * @return static
+ */
+ public function startOfMonth()
+ {
+ return $this->setDate($this->year, $this->month, 1)->startOfDay();
+ }
+
+ /**
+ * Resets the date to end of the month and time to 23:59:59
+ *
+ * @return static
+ */
+ public function endOfMonth()
+ {
+ return $this->setDate($this->year, $this->month, $this->daysInMonth)->endOfDay();
+ }
+
+ /**
+ * Resets the date to the first day of the quarter and the time to 00:00:00
+ *
+ * @return static
+ */
+ public function startOfQuarter()
+ {
+ $month = ($this->quarter - 1) * static::MONTHS_PER_QUARTER + 1;
+
+ return $this->setDate($this->year, $month, 1)->startOfDay();
+ }
+
+ /**
+ * Resets the date to end of the quarter and time to 23:59:59
+ *
+ * @return static
+ */
+ public function endOfQuarter()
+ {
+ return $this->startOfQuarter()->addMonths(static::MONTHS_PER_QUARTER - 1)->endOfMonth();
+ }
+
+ /**
+ * Resets the date to the first day of the year and the time to 00:00:00
+ *
+ * @return static
+ */
+ public function startOfYear()
+ {
+ return $this->setDate($this->year, 1, 1)->startOfDay();
+ }
+
+ /**
+ * Resets the date to end of the year and time to 23:59:59
+ *
+ * @return static
+ */
+ public function endOfYear()
+ {
+ return $this->setDate($this->year, 12, 31)->endOfDay();
+ }
+
+ /**
+ * Resets the date to the first day of the decade and the time to 00:00:00
+ *
+ * @return static
+ */
+ public function startOfDecade()
+ {
+ $year = $this->year - $this->year % static::YEARS_PER_DECADE;
+
+ return $this->setDate($year, 1, 1)->startOfDay();
+ }
+
+ /**
+ * Resets the date to end of the decade and time to 23:59:59
+ *
+ * @return static
+ */
+ public function endOfDecade()
+ {
+ $year = $this->year - $this->year % static::YEARS_PER_DECADE + static::YEARS_PER_DECADE - 1;
+
+ return $this->setDate($year, 12, 31)->endOfDay();
+ }
+
+ /**
+ * Resets the date to the first day of the century and the time to 00:00:00
+ *
+ * @return static
+ */
+ public function startOfCentury()
+ {
+ $year = $this->year - ($this->year - 1) % static::YEARS_PER_CENTURY;
+
+ return $this->setDate($year, 1, 1)->startOfDay();
+ }
+
+ /**
+ * Resets the date to end of the century and time to 23:59:59
+ *
+ * @return static
+ */
+ public function endOfCentury()
+ {
+ $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_CENTURY + static::YEARS_PER_CENTURY;
+
+ return $this->setDate($year, 12, 31)->endOfDay();
+ }
+
+ /**
+ * Resets the date to the first day of the century and the time to 00:00:00
+ *
+ * @return static
+ */
+ public function startOfMillennium()
+ {
+ $year = $this->year - ($this->year - 1) % static::YEARS_PER_MILLENNIUM;
+
+ return $this->setDate($year, 1, 1)->startOfDay();
+ }
+
+ /**
+ * Resets the date to end of the century and time to 23:59:59
+ *
+ * @return static
+ */
+ public function endOfMillennium()
+ {
+ $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_MILLENNIUM + static::YEARS_PER_MILLENNIUM;
+
+ return $this->setDate($year, 12, 31)->endOfDay();
+ }
+
+ /**
+ * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00
+ *
+ * @return static
+ */
+ public function startOfWeek()
+ {
+ while ($this->dayOfWeek !== static::$weekStartsAt) {
+ $this->subDay();
+ }
+
+ return $this->startOfDay();
+ }
+
+ /**
+ * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59
+ *
+ * @return static
+ */
+ public function endOfWeek()
+ {
+ while ($this->dayOfWeek !== static::$weekEndsAt) {
+ $this->addDay();
+ }
+
+ return $this->endOfDay();
+ }
+
+ /**
+ * Modify to start of current hour, minutes and seconds become 0
+ *
+ * @return static
+ */
+ public function startOfHour()
+ {
+ return $this->setTime($this->hour, 0, 0);
+ }
+
+ /**
+ * Modify to end of current hour, minutes and seconds become 59
+ *
+ * @return static
+ */
+ public function endOfHour()
+ {
+ return $this->modify("$this->hour:59:59.999999");
+ }
+
+ /**
+ * Modify to start of current minute, seconds become 0
+ *
+ * @return static
+ */
+ public function startOfMinute()
+ {
+ return $this->setTime($this->hour, $this->minute, 0);
+ }
+
+ /**
+ * Modify to end of current minute, seconds become 59
+ *
+ * @return static
+ */
+ public function endOfMinute()
+ {
+ return $this->modify("$this->hour:$this->minute:59.999999");
+ }
+
+ /**
+ * Modify to start of current minute, seconds become 0
+ *
+ * @return static
+ */
+ public function startOfSecond()
+ {
+ return $this->modify("$this->hour:$this->minute:$this->second.0");
+ }
+
+ /**
+ * Modify to end of current minute, seconds become 59
+ *
+ * @return static
+ */
+ public function endOfSecond()
+ {
+ return $this->modify("$this->hour:$this->minute:$this->second.999999");
+ }
+
+ /**
+ * Modify to midday, default to self::$midDayAt
+ *
+ * @return static
+ */
+ public function midDay()
+ {
+ return $this->setTime(self::$midDayAt, 0, 0);
+ }
+
+ /**
+ * Modify to the next occurrence of a given day of the week.
+ * If no dayOfWeek is provided, modify to the next occurrence
+ * of the current day of the week. Use the supplied constants
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int|null $dayOfWeek
+ *
+ * @return static
+ */
+ public function next($dayOfWeek = null)
+ {
+ if ($dayOfWeek === null) {
+ $dayOfWeek = $this->dayOfWeek;
+ }
+
+ return $this->startOfDay()->modify('next '.static::$days[$dayOfWeek]);
+ }
+
+ /**
+ * Go forward or backward to the next week- or weekend-day.
+ *
+ * @param bool $weekday
+ * @param bool $forward
+ *
+ * @return $this
+ */
+ private function nextOrPreviousDay($weekday = true, $forward = true)
+ {
+ $step = $forward ? 1 : -1;
+
+ do {
+ $this->addDay($step);
+ } while ($weekday ? $this->isWeekend() : $this->isWeekday());
+
+ return $this;
+ }
+
+ /**
+ * Go forward to the next weekday.
+ *
+ * @return $this
+ */
+ public function nextWeekday()
+ {
+ return $this->nextOrPreviousDay();
+ }
+
+ /**
+ * Go backward to the previous weekday.
+ *
+ * @return $this
+ */
+ public function previousWeekday()
+ {
+ return $this->nextOrPreviousDay(true, false);
+ }
+
+ /**
+ * Go forward to the next weekend day.
+ *
+ * @return $this
+ */
+ public function nextWeekendDay()
+ {
+ return $this->nextOrPreviousDay(false);
+ }
+
+ /**
+ * Go backward to the previous weekend day.
+ *
+ * @return $this
+ */
+ public function previousWeekendDay()
+ {
+ return $this->nextOrPreviousDay(false, false);
+ }
+
+ /**
+ * Modify to the previous occurrence of a given day of the week.
+ * If no dayOfWeek is provided, modify to the previous occurrence
+ * of the current day of the week. Use the supplied constants
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int|null $dayOfWeek
+ *
+ * @return static
+ */
+ public function previous($dayOfWeek = null)
+ {
+ if ($dayOfWeek === null) {
+ $dayOfWeek = $this->dayOfWeek;
+ }
+
+ return $this->startOfDay()->modify('last '.static::$days[$dayOfWeek]);
+ }
+
+ /**
+ * Modify to the first occurrence of a given day of the week
+ * in the current month. If no dayOfWeek is provided, modify to the
+ * first day of the current month. Use the supplied constants
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int|null $dayOfWeek
+ *
+ * @return static
+ */
+ public function firstOfMonth($dayOfWeek = null)
+ {
+ $this->startOfDay();
+
+ if ($dayOfWeek === null) {
+ return $this->day(1);
+ }
+
+ return $this->modify('first '.static::$days[$dayOfWeek].' of '.$this->format('F').' '.$this->year);
+ }
+
+ /**
+ * Modify to the last occurrence of a given day of the week
+ * in the current month. If no dayOfWeek is provided, modify to the
+ * last day of the current month. Use the supplied constants
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int|null $dayOfWeek
+ *
+ * @return static
+ */
+ public function lastOfMonth($dayOfWeek = null)
+ {
+ $this->startOfDay();
+
+ if ($dayOfWeek === null) {
+ return $this->day($this->daysInMonth);
+ }
+
+ return $this->modify('last '.static::$days[$dayOfWeek].' of '.$this->format('F').' '.$this->year);
+ }
+
+ /**
+ * Modify to the given occurrence of a given day of the week
+ * in the current month. If the calculated occurrence is outside the scope
+ * of the current month, then return false and no modifications are made.
+ * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int $nth
+ * @param int $dayOfWeek
+ *
+ * @return mixed
+ */
+ public function nthOfMonth($nth, $dayOfWeek)
+ {
+ $date = $this->copy()->firstOfMonth();
+ $check = $date->format('Y-m');
+ $date->modify('+'.$nth.' '.static::$days[$dayOfWeek]);
+
+ return $date->format('Y-m') === $check ? $this->modify($date) : false;
+ }
+
+ /**
+ * Modify to the first occurrence of a given day of the week
+ * in the current quarter. If no dayOfWeek is provided, modify to the
+ * first day of the current quarter. Use the supplied constants
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int|null $dayOfWeek day of the week default null
+ *
+ * @return static
+ */
+ public function firstOfQuarter($dayOfWeek = null)
+ {
+ return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER - 2, 1)->firstOfMonth($dayOfWeek);
+ }
+
+ /**
+ * Modify to the last occurrence of a given day of the week
+ * in the current quarter. If no dayOfWeek is provided, modify to the
+ * last day of the current quarter. Use the supplied constants
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int|null $dayOfWeek day of the week default null
+ *
+ * @return static
+ */
+ public function lastOfQuarter($dayOfWeek = null)
+ {
+ return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER, 1)->lastOfMonth($dayOfWeek);
+ }
+
+ /**
+ * Modify to the given occurrence of a given day of the week
+ * in the current quarter. If the calculated occurrence is outside the scope
+ * of the current quarter, then return false and no modifications are made.
+ * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int $nth
+ * @param int $dayOfWeek
+ *
+ * @return mixed
+ */
+ public function nthOfQuarter($nth, $dayOfWeek)
+ {
+ $date = $this->copy()->day(1)->month($this->quarter * static::MONTHS_PER_QUARTER);
+ $lastMonth = $date->month;
+ $year = $date->year;
+ $date->firstOfQuarter()->modify('+'.$nth.' '.static::$days[$dayOfWeek]);
+
+ return ($lastMonth < $date->month || $year !== $date->year) ? false : $this->modify($date);
+ }
+
+ /**
+ * Modify to the first occurrence of a given day of the week
+ * in the current year. If no dayOfWeek is provided, modify to the
+ * first day of the current year. Use the supplied constants
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int|null $dayOfWeek day of the week default null
+ *
+ * @return static
+ */
+ public function firstOfYear($dayOfWeek = null)
+ {
+ return $this->month(1)->firstOfMonth($dayOfWeek);
+ }
+
+ /**
+ * Modify to the last occurrence of a given day of the week
+ * in the current year. If no dayOfWeek is provided, modify to the
+ * last day of the current year. Use the supplied constants
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int|null $dayOfWeek day of the week default null
+ *
+ * @return static
+ */
+ public function lastOfYear($dayOfWeek = null)
+ {
+ return $this->month(static::MONTHS_PER_YEAR)->lastOfMonth($dayOfWeek);
+ }
+
+ /**
+ * Modify to the given occurrence of a given day of the week
+ * in the current year. If the calculated occurrence is outside the scope
+ * of the current year, then return false and no modifications are made.
+ * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY.
+ *
+ * @param int $nth
+ * @param int $dayOfWeek
+ *
+ * @return mixed
+ */
+ public function nthOfYear($nth, $dayOfWeek)
+ {
+ $date = $this->copy()->firstOfYear()->modify('+'.$nth.' '.static::$days[$dayOfWeek]);
+
+ return $this->year === $date->year ? $this->modify($date) : false;
+ }
+
+ /**
+ * Modify the current instance to the average of a given instance (default now) and the current instance.
+ *
+ * @param \Carbon\Carbon|\DateTimeInterface|string|null $date
+ *
+ * @return static
+ */
+ public function average($date = null)
+ {
+ $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;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /////////////////////////// SERIALIZATION /////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Return a serialized string of the instance.
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize($this);
+ }
+
+ /**
+ * Create an instance from a serialized string.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return static
+ */
+ public static function fromSerialized($value)
+ {
+ $instance = @unserialize($value);
+
+ if (!$instance instanceof static) {
+ throw new InvalidArgumentException('Invalid serialized value.');
+ }
+
+ return $instance;
+ }
+
+ /**
+ * The __set_state handler.
+ *
+ * @param array $array
+ *
+ * @return static
+ */
+ public static function __set_state($array)
+ {
+ return static::instance(parent::__set_state($array));
+ }
+
+ /**
+ * Prepare the object for JSON serialization.
+ *
+ * @return array|string
+ */
+ public function jsonSerialize()
+ {
+ if (static::$serializer) {
+ return call_user_func(static::$serializer, $this);
+ }
+
+ $carbon = $this;
+
+ return call_user_func(function () use ($carbon) {
+ return get_object_vars($carbon);
+ });
+ }
+
+ /**
+ * JSON serialize all Carbon instances using the given callback.
+ *
+ * @param callable $callback
+ *
+ * @return void
+ */
+ public static function serializeUsing($callback)
+ {
+ static::$serializer = $callback;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /////////////////////////////// MACRO /////////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Register a custom macro.
+ *
+ * @param string $name
+ * @param object|callable $macro
+ *
+ * @return void
+ */
+ public static function macro($name, $macro)
+ {
+ static::$localMacros[$name] = $macro;
+ }
+
+ /**
+ * Remove all macros.
+ */
+ public static function resetMacros()
+ {
+ static::$localMacros = array();
+ }
+
+ /**
+ * Mix another object into the class.
+ *
+ * @param object $mixin
+ *
+ * @return void
+ */
+ public static function mixin($mixin)
+ {
+ $reflection = new \ReflectionClass($mixin);
+ $methods = $reflection->getMethods(
+ \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED
+ );
+
+ foreach ($methods as $method) {
+ $method->setAccessible(true);
+
+ static::macro($method->name, $method->invoke($mixin));
+ }
+ }
+
+ /**
+ * Checks if macro is registered.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public static function hasMacro($name)
+ {
+ return isset(static::$localMacros[$name]);
+ }
+
+ /**
+ * Dynamically handle calls to the class.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @throws \BadMethodCallException
+ *
+ * @return mixed
+ */
+ public static function __callStatic($method, $parameters)
+ {
+ if (!static::hasMacro($method)) {
+ throw new \BadMethodCallException("Method $method does not exist.");
+ }
+
+ if (static::$localMacros[$method] instanceof Closure && method_exists('Closure', 'bind')) {
+ return call_user_func_array(Closure::bind(static::$localMacros[$method], null, get_called_class()), $parameters);
+ }
+
+ return call_user_func_array(static::$localMacros[$method], $parameters);
+ }
+
+ /**
+ * Dynamically handle calls to the class.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @throws \BadMethodCallException|\ReflectionException
+ *
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ if (!static::hasMacro($method)) {
+ throw new \BadMethodCallException("Method $method does not exist.");
+ }
+
+ $macro = static::$localMacros[$method];
+
+ $reflexion = new \ReflectionFunction($macro);
+ $reflectionParameters = $reflexion->getParameters();
+ $expectedCount = count($reflectionParameters);
+ $actualCount = count($parameters);
+ if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') {
+ for ($i = $actualCount; $i < $expectedCount - 1; $i++) {
+ $parameters[] = $reflectionParameters[$i]->getDefaultValue();
+ }
+ $parameters[] = $this;
+ }
+
+ if ($macro instanceof Closure && method_exists($macro, 'bindTo')) {
+ return call_user_func_array($macro->bindTo($this, get_class($this)), $parameters);
+ }
+
+ return call_user_func_array($macro, $parameters);
+ }
+
+ /**
+ * Show truthy properties on var_dump().
+ *
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ return array_filter(get_object_vars($this), function ($var) {
+ return $var;
+ });
+ }
+
+ /**
+ * Cast the current instance into the given class.
+ *
+ * @param string $className The $className::instance() method will be called to cast the current object.
+ *
+ * @return object
+ */
+ public function cast($className)
+ {
+ if (!method_exists($className, 'instance')) {
+ throw new \InvalidArgumentException("$className has not the instance() method needed to cast the date.");
+ }
+
+ return $className::instance($this);
+ }
+}
diff --git a/core/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php b/core/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php
new file mode 100644
index 0000000..e8c6032
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php
@@ -0,0 +1,1163 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Carbon;
+
+use Closure;
+use DateInterval;
+use InvalidArgumentException;
+use ReflectionClass;
+use ReflectionFunction;
+use ReflectionMethod;
+use Symfony\Component\Translation\TranslatorInterface;
+
+/**
+ * A simple API extension for DateInterval.
+ * The implementation provides helpers to handle weeks but only days are saved.
+ * Weeks are calculated based on the total days of the current instance.
+ *
+ * @property int $years Total years of the current interval.
+ * @property int $months Total months of the current interval.
+ * @property int $weeks Total weeks of the current interval calculated from the days.
+ * @property int $dayz Total days of the current interval (weeks * 7 + days).
+ * @property int $hours Total hours of the current interval.
+ * @property int $minutes Total minutes of the current interval.
+ * @property int $seconds Total seconds of the current interval.
+ * @property-read int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7).
+ * @property-read int $daysExcludeWeeks alias of dayzExcludeWeeks
+ * @property-read float $totalYears Number of years equivalent to the interval.
+ * @property-read float $totalMonths Number of months equivalent to the interval.
+ * @property-read float $totalWeeks Number of weeks equivalent to the interval.
+ * @property-read float $totalDays Number of days equivalent to the interval.
+ * @property-read float $totalDayz Alias for totalDays.
+ * @property-read float $totalHours Number of hours equivalent to the interval.
+ * @property-read float $totalMinutes Number of minutes equivalent to the interval.
+ * @property-read float $totalSeconds Number of seconds equivalent to the interval.
+ *
+ * @method static CarbonInterval years($years = 1) Create instance specifying a number of years.
+ * @method static CarbonInterval year($years = 1) Alias for years()
+ * @method static CarbonInterval months($months = 1) Create instance specifying a number of months.
+ * @method static CarbonInterval month($months = 1) Alias for months()
+ * @method static CarbonInterval weeks($weeks = 1) Create instance specifying a number of weeks.
+ * @method static CarbonInterval week($weeks = 1) Alias for weeks()
+ * @method static CarbonInterval days($days = 1) Create instance specifying a number of days.
+ * @method static CarbonInterval dayz($days = 1) Alias for days()
+ * @method static CarbonInterval day($days = 1) Alias for days()
+ * @method static CarbonInterval hours($hours = 1) Create instance specifying a number of hours.
+ * @method static CarbonInterval hour($hours = 1) Alias for hours()
+ * @method static CarbonInterval minutes($minutes = 1) Create instance specifying a number of minutes.
+ * @method static CarbonInterval minute($minutes = 1) Alias for minutes()
+ * @method static CarbonInterval seconds($seconds = 1) Create instance specifying a number of seconds.
+ * @method static CarbonInterval second($seconds = 1) Alias for seconds()
+ * @method CarbonInterval years($years = 1) Set the years portion of the current interval.
+ * @method CarbonInterval year($years = 1) Alias for years().
+ * @method CarbonInterval months($months = 1) Set the months portion of the current interval.
+ * @method CarbonInterval month($months = 1) Alias for months().
+ * @method CarbonInterval weeks($weeks = 1) Set the weeks portion of the current interval. Will overwrite dayz value.
+ * @method CarbonInterval week($weeks = 1) Alias for weeks().
+ * @method CarbonInterval days($days = 1) Set the days portion of the current interval.
+ * @method CarbonInterval dayz($days = 1) Alias for days().
+ * @method CarbonInterval day($days = 1) Alias for days().
+ * @method CarbonInterval hours($hours = 1) Set the hours portion of the current interval.
+ * @method CarbonInterval hour($hours = 1) Alias for hours().
+ * @method CarbonInterval minutes($minutes = 1) Set the minutes portion of the current interval.
+ * @method CarbonInterval minute($minutes = 1) Alias for minutes().
+ * @method CarbonInterval seconds($seconds = 1) Set the seconds portion of the current interval.
+ * @method CarbonInterval second($seconds = 1) Alias for seconds().
+ */
+class CarbonInterval extends DateInterval
+{
+ /**
+ * Interval spec period designators
+ */
+ const PERIOD_PREFIX = 'P';
+ const PERIOD_YEARS = 'Y';
+ const PERIOD_MONTHS = 'M';
+ const PERIOD_DAYS = 'D';
+ const PERIOD_TIME_PREFIX = 'T';
+ const PERIOD_HOURS = 'H';
+ const PERIOD_MINUTES = 'M';
+ const PERIOD_SECONDS = 'S';
+
+ /**
+ * A translator to ... er ... translate stuff
+ *
+ * @var \Symfony\Component\Translation\TranslatorInterface
+ */
+ protected static $translator;
+
+ /**
+ * @var array|null
+ */
+ protected static $cascadeFactors;
+
+ /**
+ * @var array|null
+ */
+ private static $flipCascadeFactors;
+
+ /**
+ * The registered macros.
+ *
+ * @var array
+ */
+ protected static $macros = array();
+
+ /**
+ * Before PHP 5.4.20/5.5.4 instead of FALSE days will be set to -99999 when the interval instance
+ * was created by DateTime::diff().
+ */
+ const PHP_DAYS_FALSE = -99999;
+
+ /**
+ * Mapping of units and factors for cascading.
+ *
+ * Should only be modified by changing the factors or referenced constants.
+ *
+ * @return array
+ */
+ public static function getCascadeFactors()
+ {
+ return static::$cascadeFactors ?: array(
+ 'minutes' => array(Carbon::SECONDS_PER_MINUTE, 'seconds'),
+ 'hours' => array(Carbon::MINUTES_PER_HOUR, 'minutes'),
+ 'dayz' => array(Carbon::HOURS_PER_DAY, 'hours'),
+ 'months' => array(Carbon::DAYS_PER_WEEK * Carbon::WEEKS_PER_MONTH, 'dayz'),
+ 'years' => array(Carbon::MONTHS_PER_YEAR, 'months'),
+ );
+ }
+
+ private static function standardizeUnit($unit)
+ {
+ $unit = rtrim($unit, 'sz').'s';
+
+ return $unit === 'days' ? 'dayz' : $unit;
+ }
+
+ private static function getFlipCascadeFactors()
+ {
+ if (!self::$flipCascadeFactors) {
+ self::$flipCascadeFactors = array();
+ foreach (static::getCascadeFactors() as $to => $tuple) {
+ list($factor, $from) = $tuple;
+
+ self::$flipCascadeFactors[self::standardizeUnit($from)] = array(self::standardizeUnit($to), $factor);
+ }
+ }
+
+ return self::$flipCascadeFactors;
+ }
+
+ /**
+ * @param array $cascadeFactors
+ */
+ public static function setCascadeFactors(array $cascadeFactors)
+ {
+ self::$flipCascadeFactors = null;
+ static::$cascadeFactors = $cascadeFactors;
+ }
+
+ /**
+ * Determine if the interval was created via DateTime:diff() or not.
+ *
+ * @param DateInterval $interval
+ *
+ * @return bool
+ */
+ private static function wasCreatedFromDiff(DateInterval $interval)
+ {
+ return $interval->days !== false && $interval->days !== static::PHP_DAYS_FALSE;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ //////////////////////////// CONSTRUCTORS /////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Create a new CarbonInterval instance.
+ *
+ * @param int $years
+ * @param int $months
+ * @param int $weeks
+ * @param int $days
+ * @param int $hours
+ * @param int $minutes
+ * @param int $seconds
+ */
+ public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null)
+ {
+ $spec = $years;
+
+ if (!is_string($spec) || floatval($years) || preg_match('/^[0-9.]/', $years)) {
+ $spec = static::PERIOD_PREFIX;
+
+ $spec .= $years > 0 ? $years.static::PERIOD_YEARS : '';
+ $spec .= $months > 0 ? $months.static::PERIOD_MONTHS : '';
+
+ $specDays = 0;
+ $specDays += $weeks > 0 ? $weeks * static::getDaysPerWeek() : 0;
+ $specDays += $days > 0 ? $days : 0;
+
+ $spec .= $specDays > 0 ? $specDays.static::PERIOD_DAYS : '';
+
+ if ($hours > 0 || $minutes > 0 || $seconds > 0) {
+ $spec .= static::PERIOD_TIME_PREFIX;
+ $spec .= $hours > 0 ? $hours.static::PERIOD_HOURS : '';
+ $spec .= $minutes > 0 ? $minutes.static::PERIOD_MINUTES : '';
+ $spec .= $seconds > 0 ? $seconds.static::PERIOD_SECONDS : '';
+ }
+
+ if ($spec === static::PERIOD_PREFIX) {
+ // Allow the zero interval.
+ $spec .= '0'.static::PERIOD_YEARS;
+ }
+ }
+
+ parent::__construct($spec);
+ }
+
+ /**
+ * Returns the factor for a given source-to-target couple.
+ *
+ * @param string $source
+ * @param string $target
+ *
+ * @return int|null
+ */
+ public static function getFactor($source, $target)
+ {
+ $source = self::standardizeUnit($source);
+ $target = self::standardizeUnit($target);
+ $factors = static::getFlipCascadeFactors();
+ if (isset($factors[$source])) {
+ list($to, $factor) = $factors[$source];
+ if ($to === $target) {
+ return $factor;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns current config for days per week.
+ *
+ * @return int
+ */
+ public static function getDaysPerWeek()
+ {
+ return static::getFactor('dayz', 'weeks') ?: Carbon::DAYS_PER_WEEK;
+ }
+
+ /**
+ * Returns current config for hours per day.
+ *
+ * @return int
+ */
+ public static function getHoursPerDay()
+ {
+ return static::getFactor('hours', 'dayz') ?: Carbon::HOURS_PER_DAY;
+ }
+
+ /**
+ * Returns current config for minutes per hour.
+ *
+ * @return int
+ */
+ public static function getMinutesPerHours()
+ {
+ return static::getFactor('minutes', 'hours') ?: Carbon::MINUTES_PER_HOUR;
+ }
+
+ /**
+ * Returns current config for seconds per minute.
+ *
+ * @return int
+ */
+ public static function getSecondsPerMinutes()
+ {
+ return static::getFactor('seconds', 'minutes') ?: Carbon::SECONDS_PER_MINUTE;
+ }
+
+ /**
+ * Create a new CarbonInterval instance from specific values.
+ * This is an alias for the constructor that allows better fluent
+ * syntax as it allows you to do CarbonInterval::create(1)->fn() rather than
+ * (new CarbonInterval(1))->fn().
+ *
+ * @param int $years
+ * @param int $months
+ * @param int $weeks
+ * @param int $days
+ * @param int $hours
+ * @param int $minutes
+ * @param int $seconds
+ *
+ * @return static
+ */
+ public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null)
+ {
+ return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds);
+ }
+
+ /**
+ * Get a copy of the instance.
+ *
+ * @return static
+ */
+ public function copy()
+ {
+ $date = new static($this->spec());
+ $date->invert = $this->invert;
+
+ return $date;
+ }
+
+ /**
+ * Provide static helpers to create instances. Allows CarbonInterval::years(3).
+ *
+ * Note: This is done using the magic method to allow static and instance methods to
+ * have the same names.
+ *
+ * @param string $name
+ * @param array $args
+ *
+ * @return static
+ */
+ public static function __callStatic($name, $args)
+ {
+ $arg = count($args) === 0 ? 1 : $args[0];
+
+ switch ($name) {
+ case 'years':
+ case 'year':
+ return new static($arg);
+
+ case 'months':
+ case 'month':
+ return new static(null, $arg);
+
+ case 'weeks':
+ case 'week':
+ return new static(null, null, $arg);
+
+ case 'days':
+ case 'dayz':
+ case 'day':
+ return new static(null, null, null, $arg);
+
+ case 'hours':
+ case 'hour':
+ return new static(null, null, null, null, $arg);
+
+ case 'minutes':
+ case 'minute':
+ return new static(null, null, null, null, null, $arg);
+
+ case 'seconds':
+ case 'second':
+ return new static(null, null, null, null, null, null, $arg);
+ }
+
+ if (static::hasMacro($name)) {
+ return call_user_func_array(
+ array(new static(0), $name), $args
+ );
+ }
+ }
+
+ /**
+ * Creates a CarbonInterval from string.
+ *
+ * Format:
+ *
+ * Suffix | Unit | Example | DateInterval expression
+ * -------|---------|---------|------------------------
+ * y | years | 1y | P1Y
+ * mo | months | 3mo | P3M
+ * w | weeks | 2w | P2W
+ * d | days | 28d | P28D
+ * h | hours | 4h | PT4H
+ * m | minutes | 12m | PT12M
+ * s | seconds | 59s | PT59S
+ *
+ * e. g. `1w 3d 4h 32m 23s` is converted to 10 days 4 hours 32 minutes and 23 seconds.
+ *
+ * Special cases:
+ * - An empty string will return a zero interval
+ * - Fractions are allowed for weeks, days, hours and minutes and will be converted
+ * and rounded to the next smaller value (caution: 0.5w = 4d)
+ *
+ * @param string $intervalDefinition
+ *
+ * @return static
+ */
+ public static function fromString($intervalDefinition)
+ {
+ if (empty($intervalDefinition)) {
+ return new static(0);
+ }
+
+ $years = 0;
+ $months = 0;
+ $weeks = 0;
+ $days = 0;
+ $hours = 0;
+ $minutes = 0;
+ $seconds = 0;
+
+ $pattern = '/(\d+(?:\.\d+)?)\h*([^\d\h]*)/i';
+ preg_match_all($pattern, $intervalDefinition, $parts, PREG_SET_ORDER);
+ while ($match = array_shift($parts)) {
+ list($part, $value, $unit) = $match;
+ $intValue = intval($value);
+ $fraction = floatval($value) - $intValue;
+ switch (strtolower($unit)) {
+ case 'year':
+ case 'years':
+ case 'y':
+ $years += $intValue;
+ break;
+
+ case 'month':
+ case 'months':
+ case 'mo':
+ $months += $intValue;
+ break;
+
+ case 'week':
+ case 'weeks':
+ case 'w':
+ $weeks += $intValue;
+ if ($fraction) {
+ $parts[] = array(null, $fraction * static::getDaysPerWeek(), 'd');
+ }
+ break;
+
+ case 'day':
+ case 'days':
+ case 'd':
+ $days += $intValue;
+ if ($fraction) {
+ $parts[] = array(null, $fraction * static::getHoursPerDay(), 'h');
+ }
+ break;
+
+ case 'hour':
+ case 'hours':
+ case 'h':
+ $hours += $intValue;
+ if ($fraction) {
+ $parts[] = array(null, $fraction * static::getMinutesPerHours(), 'm');
+ }
+ break;
+
+ case 'minute':
+ case 'minutes':
+ case 'm':
+ $minutes += $intValue;
+ if ($fraction) {
+ $seconds += round($fraction * static::getSecondsPerMinutes());
+ }
+ break;
+
+ case 'second':
+ case 'seconds':
+ case 's':
+ $seconds += $intValue;
+ break;
+
+ default:
+ throw new InvalidArgumentException(
+ sprintf('Invalid part %s in definition %s', $part, $intervalDefinition)
+ );
+ }
+ }
+
+ return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds);
+ }
+
+ /**
+ * Create a CarbonInterval instance from a DateInterval one. Can not instance
+ * 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, $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;
+ }
+
+ /**
+ * Make a CarbonInterval instance from given variable if possible.
+ *
+ * Always return a new instance. Parse only strings and only these likely to be intervals (skip dates
+ * and recurrences). Throw an exception for invalid format, but otherwise return null.
+ *
+ * @param mixed $var
+ *
+ * @return static|null
+ */
+ public static function make($var)
+ {
+ if ($var instanceof DateInterval) {
+ return static::instance($var);
+ }
+
+ if (is_string($var)) {
+ $var = trim($var);
+
+ if (substr($var, 0, 1) === 'P') {
+ return new static($var);
+ }
+
+ if (preg_match('/^(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+$/i', $var)) {
+ return static::fromString($var);
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /////////////////////// LOCALIZATION //////////////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Initialize the translator instance if necessary.
+ *
+ * @return \Symfony\Component\Translation\TranslatorInterface
+ */
+ protected static function translator()
+ {
+ if (static::$translator === null) {
+ static::$translator = Translator::get();
+ }
+
+ return static::$translator;
+ }
+
+ /**
+ * Get the translator instance in use.
+ *
+ * @return \Symfony\Component\Translation\TranslatorInterface
+ */
+ public static function getTranslator()
+ {
+ return static::translator();
+ }
+
+ /**
+ * Set the translator instance to use.
+ *
+ * @param TranslatorInterface $translator
+ */
+ public static function setTranslator(TranslatorInterface $translator)
+ {
+ static::$translator = $translator;
+ }
+
+ /**
+ * Get the current translator locale.
+ *
+ * @return string
+ */
+ public static function getLocale()
+ {
+ return static::translator()->getLocale();
+ }
+
+ /**
+ * Set the current translator locale.
+ *
+ * @param string $locale
+ */
+ public static function setLocale($locale)
+ {
+ return static::translator()->setLocale($locale) !== false;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ ///////////////////////// GETTERS AND SETTERS /////////////////////
+ ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Get a part of the CarbonInterval object.
+ *
+ * @param string $name
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return int|float
+ */
+ public function __get($name)
+ {
+ if (substr($name, 0, 5) === 'total') {
+ return $this->total(substr($name, 5));
+ }
+
+ switch ($name) {
+ case 'years':
+ return $this->y;
+
+ case 'months':
+ return $this->m;
+
+ case 'dayz':
+ return $this->d;
+
+ case 'hours':
+ return $this->h;
+
+ case 'minutes':
+ return $this->i;
+
+ case 'seconds':
+ return $this->s;
+
+ case 'weeks':
+ return (int) floor($this->d / static::getDaysPerWeek());
+
+ case 'daysExcludeWeeks':
+ case 'dayzExcludeWeeks':
+ return $this->d % static::getDaysPerWeek();
+
+ default:
+ throw new InvalidArgumentException(sprintf("Unknown getter '%s'", $name));
+ }
+ }
+
+ /**
+ * Set a part of the CarbonInterval object.
+ *
+ * @param string $name
+ * @param int $val
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __set($name, $val)
+ {
+ switch ($name) {
+ case 'years':
+ $this->y = $val;
+ break;
+
+ case 'months':
+ $this->m = $val;
+ break;
+
+ case 'weeks':
+ $this->d = $val * static::getDaysPerWeek();
+ break;
+
+ case 'dayz':
+ $this->d = $val;
+ break;
+
+ case 'hours':
+ $this->h = $val;
+ break;
+
+ case 'minutes':
+ $this->i = $val;
+ break;
+
+ case 'seconds':
+ $this->s = $val;
+ break;
+ }
+ }
+
+ /**
+ * Allow setting of weeks and days to be cumulative.
+ *
+ * @param int $weeks Number of weeks to set
+ * @param int $days Number of days to set
+ *
+ * @return static
+ */
+ public function weeksAndDays($weeks, $days)
+ {
+ $this->dayz = ($weeks * static::getDaysPerWeek()) + $days;
+
+ return $this;
+ }
+
+ /**
+ * Register a custom macro.
+ *
+ * @param string $name
+ * @param object|callable $macro
+ *
+ * @return void
+ */
+ public static function macro($name, $macro)
+ {
+ static::$macros[$name] = $macro;
+ }
+
+ /**
+ * Remove all macros.
+ */
+ public static function resetMacros()
+ {
+ static::$macros = array();
+ }
+
+ /**
+ * Register macros from a mixin object.
+ *
+ * @param object $mixin
+ *
+ * @throws \ReflectionException
+ *
+ * @return void
+ */
+ public static function mixin($mixin)
+ {
+ $reflection = new ReflectionClass($mixin);
+
+ $methods = $reflection->getMethods(
+ ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
+ );
+
+ foreach ($methods as $method) {
+ $method->setAccessible(true);
+
+ static::macro($method->name, $method->invoke($mixin));
+ }
+ }
+
+ /**
+ * Check if macro is registered.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public static function hasMacro($name)
+ {
+ return isset(static::$macros[$name]);
+ }
+
+ /**
+ * Call given macro.
+ *
+ * @param string $name
+ * @param array $parameters
+ *
+ * @return mixed
+ */
+ protected function callMacro($name, $parameters)
+ {
+ $macro = static::$macros[$name];
+
+ $reflection = new ReflectionFunction($macro);
+
+ $reflectionParameters = $reflection->getParameters();
+
+ $expectedCount = count($reflectionParameters);
+ $actualCount = count($parameters);
+
+ if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') {
+ for ($i = $actualCount; $i < $expectedCount - 1; $i++) {
+ $parameters[] = $reflectionParameters[$i]->getDefaultValue();
+ }
+
+ $parameters[] = $this;
+ }
+
+ if ($macro instanceof Closure && method_exists($macro, 'bindTo')) {
+ $macro = $macro->bindTo($this, get_class($this));
+ }
+
+ return call_user_func_array($macro, $parameters);
+ }
+
+ /**
+ * Allow fluent calls on the setters... CarbonInterval::years(3)->months(5)->day().
+ *
+ * Note: This is done using the magic method to allow static and instance methods to
+ * have the same names.
+ *
+ * @param string $name
+ * @param array $args
+ *
+ * @return static
+ */
+ public function __call($name, $args)
+ {
+ if (static::hasMacro($name)) {
+ return $this->callMacro($name, $args);
+ }
+
+ $arg = count($args) === 0 ? 1 : $args[0];
+
+ switch ($name) {
+ case 'years':
+ case 'year':
+ $this->years = $arg;
+ break;
+
+ case 'months':
+ case 'month':
+ $this->months = $arg;
+ break;
+
+ case 'weeks':
+ case 'week':
+ $this->dayz = $arg * static::getDaysPerWeek();
+ break;
+
+ case 'days':
+ case 'dayz':
+ case 'day':
+ $this->dayz = $arg;
+ break;
+
+ case 'hours':
+ case 'hour':
+ $this->hours = $arg;
+ break;
+
+ case 'minutes':
+ case 'minute':
+ $this->minutes = $arg;
+ break;
+
+ case 'seconds':
+ case 'second':
+ $this->seconds = $arg;
+ break;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the current interval in a human readable format in the current locale.
+ *
+ * @param bool $short (false by default), returns short units if true
+ *
+ * @return string
+ */
+ public function forHumans($short = false)
+ {
+ $periods = array(
+ 'year' => array('y', $this->years),
+ 'month' => array('m', $this->months),
+ 'week' => array('w', $this->weeks),
+ 'day' => array('d', $this->daysExcludeWeeks),
+ 'hour' => array('h', $this->hours),
+ 'minute' => array('min', $this->minutes),
+ 'second' => array('s', $this->seconds),
+ );
+
+ $parts = array();
+ foreach ($periods as $unit => $options) {
+ list($shortUnit, $count) = $options;
+ if ($count > 0) {
+ $parts[] = static::translator()->transChoice($short ? $shortUnit : $unit, $count, array(':count' => $count));
+ }
+ }
+
+ return implode(' ', $parts);
+ }
+
+ /**
+ * Format the instance as a string using the forHumans() function.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->forHumans();
+ }
+
+ /**
+ * Convert the interval to a CarbonPeriod.
+ *
+ * @return CarbonPeriod
+ */
+ public function toPeriod()
+ {
+ return CarbonPeriod::createFromArray(
+ array_merge(array($this), func_get_args())
+ );
+ }
+
+ /**
+ * Invert the interval.
+ *
+ * @return $this
+ */
+ public function invert()
+ {
+ $this->invert = $this->invert ? 0 : 1;
+
+ return $this;
+ }
+
+ /**
+ * Add the passed interval to the current instance.
+ *
+ * @param DateInterval $interval
+ *
+ * @return static
+ */
+ public function add(DateInterval $interval)
+ {
+ $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1;
+
+ if (static::wasCreatedFromDiff($interval)) {
+ $this->dayz += $interval->days * $sign;
+ } else {
+ $this->years += $interval->y * $sign;
+ $this->months += $interval->m * $sign;
+ $this->dayz += $interval->d * $sign;
+ $this->hours += $interval->h * $sign;
+ $this->minutes += $interval->i * $sign;
+ $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;
+ }
+
+ /**
+ * Multiply current instance given number of times
+ *
+ * @param float $factor
+ *
+ * @return $this
+ */
+ public function times($factor)
+ {
+ if ($factor < 0) {
+ $this->invert = $this->invert ? 0 : 1;
+ $factor = -$factor;
+ }
+
+ $this->years = (int) round($this->years * $factor);
+ $this->months = (int) round($this->months * $factor);
+ $this->dayz = (int) round($this->dayz * $factor);
+ $this->hours = (int) round($this->hours * $factor);
+ $this->minutes = (int) round($this->minutes * $factor);
+ $this->seconds = (int) round($this->seconds * $factor);
+
+ return $this;
+ }
+
+ /**
+ * Get the interval_spec string of a date interval.
+ *
+ * @param DateInterval $interval
+ *
+ * @return string
+ */
+ public static function getDateIntervalSpec(DateInterval $interval)
+ {
+ $date = array_filter(array(
+ 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 => abs($interval->h),
+ static::PERIOD_MINUTES => abs($interval->i),
+ static::PERIOD_SECONDS => abs($interval->s),
+ ));
+
+ $specString = static::PERIOD_PREFIX;
+
+ foreach ($date as $key => $value) {
+ $specString .= $value.$key;
+ }
+
+ if (count($time) > 0) {
+ $specString .= static::PERIOD_TIME_PREFIX;
+ foreach ($time as $key => $value) {
+ $specString .= $value.$key;
+ }
+ }
+
+ return $specString === static::PERIOD_PREFIX ? 'PT0S' : $specString;
+ }
+
+ /**
+ * Get the interval_spec string.
+ *
+ * @return string
+ */
+ public function spec()
+ {
+ return static::getDateIntervalSpec($this);
+ }
+
+ /**
+ * Comparing 2 date intervals.
+ *
+ * @param DateInterval $a
+ * @param DateInterval $b
+ *
+ * @return int
+ */
+ public static function compareDateIntervals(DateInterval $a, DateInterval $b)
+ {
+ $current = Carbon::now();
+ $passed = $current->copy()->add($b);
+ $current->add($a);
+
+ if ($current < $passed) {
+ return -1;
+ }
+ if ($current > $passed) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Comparing with passed interval.
+ *
+ * @param DateInterval $interval
+ *
+ * @return int
+ */
+ public function compare(DateInterval $interval)
+ {
+ return static::compareDateIntervals($this, $interval);
+ }
+
+ /**
+ * Convert overflowed values into bigger units.
+ *
+ * @return $this
+ */
+ public function cascade()
+ {
+ foreach (static::getFlipCascadeFactors() as $source => $cascade) {
+ list($target, $factor) = $cascade;
+
+ if ($source === 'dayz' && $target === 'weeks') {
+ continue;
+ }
+
+ $value = $this->$source;
+ $this->$source = $modulo = $value % $factor;
+ $this->$target += ($value - $modulo) / $factor;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get amount of given unit equivalent to the interval.
+ *
+ * @param string $unit
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return float
+ */
+ public function total($unit)
+ {
+ $realUnit = $unit = strtolower($unit);
+
+ if (in_array($unit, array('days', 'weeks'))) {
+ $realUnit = 'dayz';
+ } elseif (!in_array($unit, array('seconds', 'minutes', 'hours', 'dayz', 'months', 'years'))) {
+ throw new InvalidArgumentException("Unknown unit '$unit'.");
+ }
+
+ $result = 0;
+ $cumulativeFactor = 0;
+ $unitFound = false;
+
+ foreach (static::getFlipCascadeFactors() as $source => $cascade) {
+ list($target, $factor) = $cascade;
+
+ if ($source === $realUnit) {
+ $unitFound = true;
+ $result += $this->$source;
+ $cumulativeFactor = 1;
+ }
+
+ if ($factor === false) {
+ if ($unitFound) {
+ break;
+ }
+
+ $result = 0;
+ $cumulativeFactor = 0;
+
+ continue;
+ }
+
+ if ($target === $realUnit) {
+ $unitFound = true;
+ }
+
+ if ($cumulativeFactor) {
+ $cumulativeFactor *= $factor;
+ $result += $this->$target * $cumulativeFactor;
+
+ continue;
+ }
+
+ $result = ($result + $this->$source) / $factor;
+ }
+
+ if (isset($target) && !$cumulativeFactor) {
+ $result += $this->$target;
+ }
+
+ if (!$unitFound) {
+ throw new \InvalidArgumentException("Unit $unit have no configuration to get total from other units.");
+ }
+
+ if ($unit === 'weeks') {
+ return $result / static::getDaysPerWeek();
+ }
+
+ return $result;
+ }
+}
diff --git a/core/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php b/core/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php
new file mode 100644
index 0000000..cb4e0f8
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php
@@ -0,0 +1,1453 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Carbon;
+
+use BadMethodCallException;
+use Closure;
+use Countable;
+use DateInterval;
+use DateTime;
+use DateTimeInterface;
+use InvalidArgumentException;
+use Iterator;
+use ReflectionClass;
+use ReflectionFunction;
+use ReflectionMethod;
+use RuntimeException;
+
+/**
+ * Substitution of DatePeriod with some modifications and many more features.
+ * Fully compatible with PHP 5.3+!
+ *
+ * @method static CarbonPeriod start($date, $inclusive = null) Create instance specifying start date.
+ * @method static CarbonPeriod since($date, $inclusive = null) Alias for start().
+ * @method static CarbonPeriod sinceNow($inclusive = null) Create instance with start date set to now.
+ * @method static CarbonPeriod end($date = null, $inclusive = null) Create instance specifying end date.
+ * @method static CarbonPeriod until($date = null, $inclusive = null) Alias for end().
+ * @method static CarbonPeriod untilNow($inclusive = null) Create instance with end date set to now.
+ * @method static CarbonPeriod dates($start, $end = null) Create instance with start and end date.
+ * @method static CarbonPeriod between($start, $end = null) Create instance with start and end date.
+ * @method static CarbonPeriod recurrences($recurrences = null) Create instance with maximum number of recurrences.
+ * @method static CarbonPeriod times($recurrences = null) Alias for recurrences().
+ * @method static CarbonPeriod options($options = null) Create instance with options.
+ * @method static CarbonPeriod toggle($options, $state = null) Create instance with options toggled on or off.
+ * @method static CarbonPeriod filter($callback, $name = null) Create instance with filter added to the stack.
+ * @method static CarbonPeriod push($callback, $name = null) Alias for filter().
+ * @method static CarbonPeriod prepend($callback, $name = null) Create instance with filter prepened to the stack.
+ * @method static CarbonPeriod filters(array $filters) Create instance with filters stack.
+ * @method static CarbonPeriod interval($interval) Create instance with given date interval.
+ * @method static CarbonPeriod each($interval) Create instance with given date interval.
+ * @method static CarbonPeriod every($interval) Create instance with given date interval.
+ * @method static CarbonPeriod step($interval) Create instance with given date interval.
+ * @method static CarbonPeriod stepBy($interval) Create instance with given date interval.
+ * @method static CarbonPeriod invert() Create instance with inverted date interval.
+ * @method static CarbonPeriod years($years = 1) Create instance specifying a number of years for date interval.
+ * @method static CarbonPeriod year($years = 1) Alias for years().
+ * @method static CarbonPeriod months($months = 1) Create instance specifying a number of months for date interval.
+ * @method static CarbonPeriod month($months = 1) Alias for months().
+ * @method static CarbonPeriod weeks($weeks = 1) Create instance specifying a number of weeks for date interval.
+ * @method static CarbonPeriod week($weeks = 1) Alias for weeks().
+ * @method static CarbonPeriod days($days = 1) Create instance specifying a number of days for date interval.
+ * @method static CarbonPeriod dayz($days = 1) Alias for days().
+ * @method static CarbonPeriod day($days = 1) Alias for days().
+ * @method static CarbonPeriod hours($hours = 1) Create instance specifying a number of hours for date interval.
+ * @method static CarbonPeriod hour($hours = 1) Alias for hours().
+ * @method static CarbonPeriod minutes($minutes = 1) Create instance specifying a number of minutes for date interval.
+ * @method static CarbonPeriod minute($minutes = 1) Alias for minutes().
+ * @method static CarbonPeriod seconds($seconds = 1) Create instance specifying a number of seconds for date interval.
+ * @method static CarbonPeriod second($seconds = 1) Alias for seconds().
+ * @method CarbonPeriod start($date, $inclusive = null) Change the period start date.
+ * @method CarbonPeriod since($date, $inclusive = null) Alias for start().
+ * @method CarbonPeriod sinceNow($inclusive = null) Change the period start date to now.
+ * @method CarbonPeriod end($date = null, $inclusive = null) Change the period end date.
+ * @method CarbonPeriod until($date = null, $inclusive = null) Alias for end().
+ * @method CarbonPeriod untilNow($inclusive = null) Change the period end date to now.
+ * @method CarbonPeriod dates($start, $end = null) Change the period start and end date.
+ * @method CarbonPeriod recurrences($recurrences = null) Change the maximum number of recurrences.
+ * @method CarbonPeriod times($recurrences = null) Alias for recurrences().
+ * @method CarbonPeriod options($options = null) Change the period options.
+ * @method CarbonPeriod toggle($options, $state = null) Toggle given options on or off.
+ * @method CarbonPeriod filter($callback, $name = null) Add a filter to the stack.
+ * @method CarbonPeriod push($callback, $name = null) Alias for filter().
+ * @method CarbonPeriod prepend($callback, $name = null) Prepend a filter to the stack.
+ * @method CarbonPeriod filters(array $filters = array()) Set filters stack.
+ * @method CarbonPeriod interval($interval) Change the period date interval.
+ * @method CarbonPeriod invert() Invert the period date interval.
+ * @method CarbonPeriod years($years = 1) Set the years portion of the date interval.
+ * @method CarbonPeriod year($years = 1) Alias for years().
+ * @method CarbonPeriod months($months = 1) Set the months portion of the date interval.
+ * @method CarbonPeriod month($months = 1) Alias for months().
+ * @method CarbonPeriod weeks($weeks = 1) Set the weeks portion of the date interval.
+ * @method CarbonPeriod week($weeks = 1) Alias for weeks().
+ * @method CarbonPeriod days($days = 1) Set the days portion of the date interval.
+ * @method CarbonPeriod dayz($days = 1) Alias for days().
+ * @method CarbonPeriod day($days = 1) Alias for days().
+ * @method CarbonPeriod hours($hours = 1) Set the hours portion of the date interval.
+ * @method CarbonPeriod hour($hours = 1) Alias for hours().
+ * @method CarbonPeriod minutes($minutes = 1) Set the minutes portion of the date interval.
+ * @method CarbonPeriod minute($minutes = 1) Alias for minutes().
+ * @method CarbonPeriod seconds($seconds = 1) Set the seconds portion of the date interval.
+ * @method CarbonPeriod second($seconds = 1) Alias for seconds().
+ */
+class CarbonPeriod implements Iterator, Countable
+{
+ /**
+ * Built-in filters.
+ *
+ * @var string
+ */
+ const RECURRENCES_FILTER = 'Carbon\CarbonPeriod::filterRecurrences';
+ const END_DATE_FILTER = 'Carbon\CarbonPeriod::filterEndDate';
+
+ /**
+ * Special value which can be returned by filters to end iteration. Also a filter.
+ *
+ * @var string
+ */
+ const END_ITERATION = 'Carbon\CarbonPeriod::endIteration';
+
+ /**
+ * Available options.
+ *
+ * @var int
+ */
+ const EXCLUDE_START_DATE = 1;
+ const EXCLUDE_END_DATE = 2;
+
+ /**
+ * Number of maximum attempts before giving up on finding next valid date.
+ *
+ * @var int
+ */
+ const NEXT_MAX_ATTEMPTS = 1000;
+
+ /**
+ * The registered macros.
+ *
+ * @var array
+ */
+ protected static $macros = array();
+
+ /**
+ * Underlying date interval instance. Always present, one day by default.
+ *
+ * @var CarbonInterval
+ */
+ protected $dateInterval;
+
+ /**
+ * Whether current date interval was set by default.
+ *
+ * @var bool
+ */
+ protected $isDefaultInterval;
+
+ /**
+ * The filters stack.
+ *
+ * @var array
+ */
+ protected $filters = array();
+
+ /**
+ * Period start date. Applied on rewind. Always present, now by default.
+ *
+ * @var Carbon
+ */
+ protected $startDate;
+
+ /**
+ * Period end date. For inverted interval should be before the start date. Applied via a filter.
+ *
+ * @var Carbon|null
+ */
+ protected $endDate;
+
+ /**
+ * Limit for number of recurrences. Applied via a filter.
+ *
+ * @var int|null
+ */
+ protected $recurrences;
+
+ /**
+ * Iteration options.
+ *
+ * @var int
+ */
+ protected $options;
+
+ /**
+ * Index of current date. Always sequential, even if some dates are skipped by filters.
+ * Equal to null only before the first iteration.
+ *
+ * @var int
+ */
+ protected $key;
+
+ /**
+ * Current date. May temporarily hold unaccepted value when looking for a next valid date.
+ * Equal to null only before the first iteration.
+ *
+ * @var Carbon
+ */
+ protected $current;
+
+ /**
+ * Timezone of current date. Taken from the start date.
+ *
+ * @var \DateTimeZone|null
+ */
+ protected $timezone;
+
+ /**
+ * The cached validation result for current date.
+ *
+ * @var bool|string|null
+ */
+ protected $validationResult;
+
+ /**
+ * Create a new instance.
+ *
+ * @return static
+ */
+ public static function create()
+ {
+ return static::createFromArray(func_get_args());
+ }
+
+ /**
+ * Create a new instance from an array of parameters.
+ *
+ * @param array $params
+ *
+ * @return static
+ */
+ public static function createFromArray(array $params)
+ {
+ // PHP 5.3 equivalent of new static(...$params).
+ $reflection = new ReflectionClass(get_class());
+ /** @var static $instance */
+ $instance = $reflection->newInstanceArgs($params);
+
+ return $instance;
+ }
+
+ /**
+ * Create CarbonPeriod from ISO 8601 string.
+ *
+ * @param string $iso
+ * @param int|null $options
+ *
+ * @return static
+ */
+ public static function createFromIso($iso, $options = null)
+ {
+ $params = static::parseIso8601($iso);
+
+ $instance = static::createFromArray($params);
+
+ if ($options !== null) {
+ $instance->setOptions($options);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Return whether given interval contains non zero value of any time unit.
+ *
+ * @param \DateInterval $interval
+ *
+ * @return bool
+ */
+ protected static function intervalHasTime(DateInterval $interval)
+ {
+ // The array_key_exists and get_object_vars are used as a workaround to check microsecond support.
+ // Both isset and property_exists will fail on PHP 7.0.14 - 7.0.21 due to the following bug:
+ // https://bugs.php.net/bug.php?id=74852
+ return $interval->h || $interval->i || $interval->s || array_key_exists('f', get_object_vars($interval)) && $interval->f;
+ }
+
+ /**
+ * Return whether given callable is a string pointing to one of Carbon's is* methods
+ * and should be automatically converted to a filter callback.
+ *
+ * @param callable $callable
+ *
+ * @return bool
+ */
+ protected static function isCarbonPredicateMethod($callable)
+ {
+ return is_string($callable) && substr($callable, 0, 2) === 'is' && (method_exists('Carbon\Carbon', $callable) || Carbon::hasMacro($callable));
+ }
+
+ /**
+ * Return whether given variable is an ISO 8601 specification.
+ *
+ * Note: Check is very basic, as actual validation will be done later when parsing.
+ * We just want to ensure that variable is not any other type of a valid parameter.
+ *
+ * @param mixed $var
+ *
+ * @return bool
+ */
+ protected static function isIso8601($var)
+ {
+ if (!is_string($var)) {
+ return false;
+ }
+
+ // Match slash but not within a timezone name.
+ $part = '[a-z]+(?:[_-][a-z]+)*';
+
+ preg_match("#\b$part/$part\b|(/)#i", $var, $match);
+
+ return isset($match[1]);
+ }
+
+ /**
+ * Parse given ISO 8601 string into an array of arguments.
+ *
+ * @param string $iso
+ *
+ * @return array
+ */
+ protected static function parseIso8601($iso)
+ {
+ $result = array();
+
+ $interval = null;
+ $start = null;
+ $end = null;
+
+ foreach (explode('/', $iso) as $key => $part) {
+ if ($key === 0 && preg_match('/^R([0-9]*)$/', $part, $match)) {
+ $parsed = strlen($match[1]) ? (int) $match[1] : null;
+ } elseif ($interval === null && $parsed = CarbonInterval::make($part)) {
+ $interval = $part;
+ } elseif ($start === null && $parsed = Carbon::make($part)) {
+ $start = $part;
+ } elseif ($end === null && $parsed = Carbon::make(static::addMissingParts($start, $part))) {
+ $end = $part;
+ } else {
+ throw new InvalidArgumentException("Invalid ISO 8601 specification: $iso.");
+ }
+
+ $result[] = $parsed;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Add missing parts of the target date from the soure date.
+ *
+ * @param string $source
+ * @param string $target
+ *
+ * @return string
+ */
+ protected static function addMissingParts($source, $target)
+ {
+ $pattern = '/'.preg_replace('/[0-9]+/', '[0-9]+', preg_quote($target, '/')).'$/';
+
+ $result = preg_replace($pattern, $target, $source, 1, $count);
+
+ return $count ? $result : $target;
+ }
+
+ /**
+ * Register a custom macro.
+ *
+ * @param string $name
+ * @param object|callable $macro
+ *
+ * @return void
+ */
+ public static function macro($name, $macro)
+ {
+ static::$macros[$name] = $macro;
+ }
+
+ /**
+ * Remove all macros.
+ */
+ public static function resetMacros()
+ {
+ static::$macros = array();
+ }
+
+ /**
+ * Register macros from a mixin object.
+ *
+ * @param object $mixin
+ *
+ * @throws \ReflectionException
+ *
+ * @return void
+ */
+ public static function mixin($mixin)
+ {
+ $reflection = new ReflectionClass($mixin);
+
+ $methods = $reflection->getMethods(
+ ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
+ );
+
+ foreach ($methods as $method) {
+ $method->setAccessible(true);
+
+ static::macro($method->name, $method->invoke($mixin));
+ }
+ }
+
+ /**
+ * Check if macro is registered.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public static function hasMacro($name)
+ {
+ return isset(static::$macros[$name]);
+ }
+
+ /**
+ * Provide static proxy for instance aliases.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @return mixed
+ */
+ public static function __callStatic($method, $parameters)
+ {
+ return call_user_func_array(
+ array(new static, $method), $parameters
+ );
+ }
+
+ /**
+ * CarbonPeriod constructor.
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct()
+ {
+ // Parse and assign arguments one by one. First argument may be an ISO 8601 spec,
+ // which will be first parsed into parts and then processed the same way.
+ $arguments = func_get_args();
+
+ if (count($arguments) && static::isIso8601($iso = $arguments[0])) {
+ array_splice($arguments, 0, 1, static::parseIso8601($iso));
+ }
+
+ foreach ($arguments as $argument) {
+ if ($this->dateInterval === null && $parsed = CarbonInterval::make($argument)) {
+ $this->setDateInterval($parsed);
+ } elseif ($this->startDate === null && $parsed = Carbon::make($argument)) {
+ $this->setStartDate($parsed);
+ } elseif ($this->endDate === null && $parsed = Carbon::make($argument)) {
+ $this->setEndDate($parsed);
+ } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) {
+ $this->setRecurrences($argument);
+ } elseif ($this->options === null && (is_int($argument) || $argument === null)) {
+ $this->setOptions($argument);
+ } else {
+ throw new InvalidArgumentException('Invalid constructor parameters.');
+ }
+ }
+
+ if ($this->startDate === null) {
+ $this->setStartDate(Carbon::now());
+ }
+
+ if ($this->dateInterval === null) {
+ $this->setDateInterval(CarbonInterval::day());
+
+ $this->isDefaultInterval = true;
+ }
+
+ if ($this->options === null) {
+ $this->setOptions(0);
+ }
+ }
+
+ /**
+ * Change the period date interval.
+ *
+ * @param DateInterval|string $interval
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function setDateInterval($interval)
+ {
+ if (!$interval = CarbonInterval::make($interval)) {
+ throw new InvalidArgumentException('Invalid interval.');
+ }
+
+ if ($interval->spec() === 'PT0S') {
+ throw new InvalidArgumentException('Empty interval is not accepted.');
+ }
+
+ $this->dateInterval = $interval;
+
+ $this->isDefaultInterval = false;
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * Invert the period date interval.
+ *
+ * @return $this
+ */
+ public function invertDateInterval()
+ {
+ $interval = $this->dateInterval->invert();
+
+ return $this->setDateInterval($interval);
+ }
+
+ /**
+ * Set start and end date.
+ *
+ * @param DateTime|DateTimeInterface|string $start
+ * @param DateTime|DateTimeInterface|string|null $end
+ *
+ * @return $this
+ */
+ public function setDates($start, $end)
+ {
+ $this->setStartDate($start);
+ $this->setEndDate($end);
+
+ return $this;
+ }
+
+ /**
+ * Change the period options.
+ *
+ * @param int|null $options
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function setOptions($options)
+ {
+ if (!is_int($options) && !is_null($options)) {
+ throw new InvalidArgumentException('Invalid options.');
+ }
+
+ $this->options = $options ?: 0;
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * Get the period options.
+ *
+ * @return int
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Toggle given options on or off.
+ *
+ * @param int $options
+ * @param bool|null $state
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function toggleOptions($options, $state = null)
+ {
+ if ($state === null) {
+ $state = ($this->options & $options) !== $options;
+ }
+
+ return $this->setOptions($state ?
+ $this->options | $options :
+ $this->options & ~$options
+ );
+ }
+
+ /**
+ * Toggle EXCLUDE_START_DATE option.
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function excludeStartDate($state = true)
+ {
+ return $this->toggleOptions(static::EXCLUDE_START_DATE, $state);
+ }
+
+ /**
+ * Toggle EXCLUDE_END_DATE option.
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function excludeEndDate($state = true)
+ {
+ return $this->toggleOptions(static::EXCLUDE_END_DATE, $state);
+ }
+
+ /**
+ * Get the underlying date interval.
+ *
+ * @return CarbonInterval
+ */
+ public function getDateInterval()
+ {
+ return $this->dateInterval->copy();
+ }
+
+ /**
+ * Get start date of the period.
+ *
+ * @return Carbon
+ */
+ public function getStartDate()
+ {
+ return $this->startDate->copy();
+ }
+
+ /**
+ * Get end date of the period.
+ *
+ * @return Carbon|null
+ */
+ public function getEndDate()
+ {
+ if ($this->endDate) {
+ return $this->endDate->copy();
+ }
+ }
+
+ /**
+ * Get number of recurrences.
+ *
+ * @return int|null
+ */
+ public function getRecurrences()
+ {
+ return $this->recurrences;
+ }
+
+ /**
+ * Returns true if the start date should be excluded.
+ *
+ * @return bool
+ */
+ public function isStartExcluded()
+ {
+ return ($this->options & static::EXCLUDE_START_DATE) !== 0;
+ }
+
+ /**
+ * Returns true if the end date should be excluded.
+ *
+ * @return bool
+ */
+ public function isEndExcluded()
+ {
+ return ($this->options & static::EXCLUDE_END_DATE) !== 0;
+ }
+
+ /**
+ * Add a filter to the stack.
+ *
+ * @param callable $callback
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function addFilter($callback, $name = null)
+ {
+ $tuple = $this->createFilterTuple(func_get_args());
+
+ $this->filters[] = $tuple;
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * Prepend a filter to the stack.
+ *
+ * @param callable $callback
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function prependFilter($callback, $name = null)
+ {
+ $tuple = $this->createFilterTuple(func_get_args());
+
+ array_unshift($this->filters, $tuple);
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * Create a filter tuple from raw parameters.
+ *
+ * Will create an automatic filter callback for one of Carbon's is* methods.
+ *
+ * @param array $parameters
+ *
+ * @return array
+ */
+ protected function createFilterTuple(array $parameters)
+ {
+ $method = array_shift($parameters);
+
+ if (!$this->isCarbonPredicateMethod($method)) {
+ return array($method, array_shift($parameters));
+ }
+
+ return array(function ($date) use ($method, $parameters) {
+ return call_user_func_array(array($date, $method), $parameters);
+ }, $method);
+ }
+
+ /**
+ * Remove a filter by instance or name.
+ *
+ * @param callable|string $filter
+ *
+ * @return $this
+ */
+ public function removeFilter($filter)
+ {
+ $key = is_callable($filter) ? 0 : 1;
+
+ $this->filters = array_values(array_filter(
+ $this->filters,
+ function ($tuple) use ($key, $filter) {
+ return $tuple[$key] !== $filter;
+ }
+ ));
+
+ $this->updateInternalState();
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * Return whether given instance or name is in the filter stack.
+ *
+ * @param callable|string $filter
+ *
+ * @return bool
+ */
+ public function hasFilter($filter)
+ {
+ $key = is_callable($filter) ? 0 : 1;
+
+ foreach ($this->filters as $tuple) {
+ if ($tuple[$key] === $filter) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get filters stack.
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ return $this->filters;
+ }
+
+ /**
+ * Set filters stack.
+ *
+ * @param array $filters
+ *
+ * @return $this
+ */
+ public function setFilters(array $filters)
+ {
+ $this->filters = $filters;
+
+ $this->updateInternalState();
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * Reset filters stack.
+ *
+ * @return $this
+ */
+ public function resetFilters()
+ {
+ $this->filters = array();
+
+ if ($this->endDate !== null) {
+ $this->filters[] = array(static::END_DATE_FILTER, null);
+ }
+
+ if ($this->recurrences !== null) {
+ $this->filters[] = array(static::RECURRENCES_FILTER, null);
+ }
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * Update properties after removing built-in filters.
+ *
+ * @return void
+ */
+ protected function updateInternalState()
+ {
+ if (!$this->hasFilter(static::END_DATE_FILTER)) {
+ $this->endDate = null;
+ }
+
+ if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
+ $this->recurrences = null;
+ }
+ }
+
+ /**
+ * Add a recurrences filter (set maximum number of recurrences).
+ *
+ * @param int|null $recurrences
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function setRecurrences($recurrences)
+ {
+ if (!is_numeric($recurrences) && !is_null($recurrences) || $recurrences < 0) {
+ throw new InvalidArgumentException('Invalid number of recurrences.');
+ }
+
+ if ($recurrences === null) {
+ return $this->removeFilter(static::RECURRENCES_FILTER);
+ }
+
+ $this->recurrences = (int) $recurrences;
+
+ if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
+ return $this->addFilter(static::RECURRENCES_FILTER);
+ }
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * Recurrences filter callback (limits number of recurrences).
+ *
+ * @param \Carbon\Carbon $current
+ * @param int $key
+ *
+ * @return bool|string
+ */
+ protected function filterRecurrences($current, $key)
+ {
+ if ($key < $this->recurrences) {
+ return true;
+ }
+
+ return static::END_ITERATION;
+ }
+
+ /**
+ * Change the period start date.
+ *
+ * @param DateTime|DateTimeInterface|string $date
+ * @param bool|null $inclusive
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function setStartDate($date, $inclusive = null)
+ {
+ if (!$date = Carbon::make($date)) {
+ throw new InvalidArgumentException('Invalid start date.');
+ }
+
+ $this->startDate = $date;
+
+ if ($inclusive !== null) {
+ $this->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Change the period end date.
+ *
+ * @param DateTime|DateTimeInterface|string|null $date
+ * @param bool|null $inclusive
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function setEndDate($date, $inclusive = null)
+ {
+ if (!is_null($date) && !$date = Carbon::make($date)) {
+ throw new InvalidArgumentException('Invalid end date.');
+ }
+
+ if (!$date) {
+ return $this->removeFilter(static::END_DATE_FILTER);
+ }
+
+ $this->endDate = $date;
+
+ if ($inclusive !== null) {
+ $this->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive);
+ }
+
+ if (!$this->hasFilter(static::END_DATE_FILTER)) {
+ return $this->addFilter(static::END_DATE_FILTER);
+ }
+
+ $this->handleChangedParameters();
+
+ return $this;
+ }
+
+ /**
+ * End date filter callback.
+ *
+ * @param \Carbon\Carbon $current
+ *
+ * @return bool|string
+ */
+ protected function filterEndDate($current)
+ {
+ if (!$this->isEndExcluded() && $current == $this->endDate) {
+ return true;
+ }
+
+ if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) {
+ return true;
+ }
+
+ return static::END_ITERATION;
+ }
+
+ /**
+ * End iteration filter callback.
+ *
+ * @return string
+ */
+ protected function endIteration()
+ {
+ return static::END_ITERATION;
+ }
+
+ /**
+ * Handle change of the parameters.
+ */
+ protected function handleChangedParameters()
+ {
+ $this->validationResult = null;
+ }
+
+ /**
+ * Validate current date and stop iteration when necessary.
+ *
+ * Returns true when current date is valid, false if it is not, or static::END_ITERATION
+ * when iteration should be stopped.
+ *
+ * @return bool|static::END_ITERATION
+ */
+ protected function validateCurrentDate()
+ {
+ if ($this->current === null) {
+ $this->rewind();
+ }
+
+ // Check after the first rewind to avoid repeating the initial validation.
+ if ($this->validationResult !== null) {
+ return $this->validationResult;
+ }
+
+ return $this->validationResult = $this->checkFilters();
+ }
+
+ /**
+ * Check whether current value and key pass all the filters.
+ *
+ * @return bool|string
+ */
+ protected function checkFilters()
+ {
+ $current = $this->prepareForReturn($this->current);
+
+ foreach ($this->filters as $tuple) {
+ $result = call_user_func(
+ $tuple[0], $current->copy(), $this->key, $this
+ );
+
+ if ($result === static::END_ITERATION) {
+ return static::END_ITERATION;
+ }
+
+ if (!$result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Prepare given date to be returned to the external logic.
+ *
+ * @param Carbon $date
+ *
+ * @return Carbon
+ */
+ protected function prepareForReturn(Carbon $date)
+ {
+ $date = $date->copy();
+
+ if ($this->timezone) {
+ $date->setTimezone($this->timezone);
+ }
+
+ return $date;
+ }
+
+ /**
+ * Check if the current position is valid.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->validateCurrentDate() === true;
+ }
+
+ /**
+ * Return the current key.
+ *
+ * @return int|null
+ */
+ public function key()
+ {
+ if ($this->valid()) {
+ return $this->key;
+ }
+ }
+
+ /**
+ * Return the current date.
+ *
+ * @return Carbon|null
+ */
+ public function current()
+ {
+ if ($this->valid()) {
+ return $this->prepareForReturn($this->current);
+ }
+ }
+
+ /**
+ * Move forward to the next date.
+ *
+ * @throws \RuntimeException
+ *
+ * @return void
+ */
+ public function next()
+ {
+ if ($this->current === null) {
+ $this->rewind();
+ }
+
+ if ($this->validationResult !== static::END_ITERATION) {
+ $this->key++;
+
+ $this->incrementCurrentDateUntilValid();
+ }
+ }
+
+ /**
+ * Rewind to the start date.
+ *
+ * Iterating over a date in the UTC timezone avoids bug during backward DST change.
+ *
+ * @see https://bugs.php.net/bug.php?id=72255
+ * @see https://bugs.php.net/bug.php?id=74274
+ * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time
+ *
+ * @throws \RuntimeException
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ $this->key = 0;
+ $this->current = $this->startDate->copy();
+ $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null;
+
+ if ($this->timezone) {
+ $this->current->setTimezone('UTC');
+ }
+
+ $this->validationResult = null;
+
+ if ($this->isStartExcluded() || $this->validateCurrentDate() === false) {
+ $this->incrementCurrentDateUntilValid();
+ }
+ }
+
+ /**
+ * Skip iterations and returns iteration state (false if ended, true if still valid).
+ *
+ * @param int $count steps number to skip (1 by default)
+ *
+ * @return bool
+ */
+ public function skip($count = 1)
+ {
+ for ($i = $count; $this->valid() && $i > 0; $i--) {
+ $this->next();
+ }
+
+ return $this->valid();
+ }
+
+ /**
+ * Keep incrementing the current date until a valid date is found or the iteration is ended.
+ *
+ * @throws \RuntimeException
+ *
+ * @return void
+ */
+ protected function incrementCurrentDateUntilValid()
+ {
+ $attempts = 0;
+
+ do {
+ $this->current->add($this->dateInterval);
+
+ $this->validationResult = null;
+
+ if (++$attempts > static::NEXT_MAX_ATTEMPTS) {
+ throw new RuntimeException('Could not find next valid date.');
+ }
+ } while ($this->validateCurrentDate() === false);
+ }
+
+ /**
+ * Format the date period as ISO 8601.
+ *
+ * @return string
+ */
+ public function toIso8601String()
+ {
+ $parts = array();
+
+ if ($this->recurrences !== null) {
+ $parts[] = 'R'.$this->recurrences;
+ }
+
+ $parts[] = $this->startDate->toIso8601String();
+
+ $parts[] = $this->dateInterval->spec();
+
+ if ($this->endDate !== null) {
+ $parts[] = $this->endDate->toIso8601String();
+ }
+
+ return implode('/', $parts);
+ }
+
+ /**
+ * Convert the date period into a string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ $translator = Carbon::getTranslator();
+
+ $parts = array();
+
+ $format = !$this->startDate->isStartOfDay() || $this->endDate && !$this->endDate->isStartOfDay()
+ ? 'Y-m-d H:i:s'
+ : 'Y-m-d';
+
+ if ($this->recurrences !== null) {
+ $parts[] = $translator->transChoice('period_recurrences', $this->recurrences, array(':count' => $this->recurrences));
+ }
+
+ $parts[] = $translator->trans('period_interval', array(':interval' => $this->dateInterval->forHumans()));
+
+ $parts[] = $translator->trans('period_start_date', array(':date' => $this->startDate->format($format)));
+
+ if ($this->endDate !== null) {
+ $parts[] = $translator->trans('period_end_date', array(':date' => $this->endDate->format($format)));
+ }
+
+ $result = implode(' ', $parts);
+
+ return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1);
+ }
+
+ /**
+ * Format the date period as ISO 8601.
+ *
+ * @return string
+ */
+ public function spec()
+ {
+ return $this->toIso8601String();
+ }
+
+ /**
+ * Convert the date period into an array without changing current iteration state.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $state = array(
+ $this->key,
+ $this->current ? $this->current->copy() : null,
+ $this->validationResult,
+ );
+
+ $result = iterator_to_array($this);
+
+ list(
+ $this->key,
+ $this->current,
+ $this->validationResult
+ ) = $state;
+
+ return $result;
+ }
+
+ /**
+ * Count dates in the date period.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->toArray());
+ }
+
+ /**
+ * Return the first date in the date period.
+ *
+ * @return Carbon|null
+ */
+ public function first()
+ {
+ if ($array = $this->toArray()) {
+ return $array[0];
+ }
+ }
+
+ /**
+ * Return the last date in the date period.
+ *
+ * @return Carbon|null
+ */
+ public function last()
+ {
+ if ($array = $this->toArray()) {
+ return $array[count($array) - 1];
+ }
+ }
+
+ /**
+ * Call given macro.
+ *
+ * @param string $name
+ * @param array $parameters
+ *
+ * @return mixed
+ */
+ protected function callMacro($name, $parameters)
+ {
+ $macro = static::$macros[$name];
+
+ $reflection = new ReflectionFunction($macro);
+
+ $reflectionParameters = $reflection->getParameters();
+
+ $expectedCount = count($reflectionParameters);
+ $actualCount = count($parameters);
+
+ if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') {
+ for ($i = $actualCount; $i < $expectedCount - 1; $i++) {
+ $parameters[] = $reflectionParameters[$i]->getDefaultValue();
+ }
+
+ $parameters[] = $this;
+ }
+
+ if ($macro instanceof Closure && method_exists($macro, 'bindTo')) {
+ $macro = $macro->bindTo($this, get_class($this));
+ }
+
+ return call_user_func_array($macro, $parameters);
+ }
+
+ /**
+ * Convert the date period into a string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Add aliases for setters.
+ *
+ * CarbonPeriod::days(3)->hours(5)->invert()
+ * ->sinceNow()->until('2010-01-10')
+ * ->filter(...)
+ * ->count()
+ *
+ * Note: We use magic method to let static and instance aliases with the same names.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ if (static::hasMacro($method)) {
+ return $this->callMacro($method, $parameters);
+ }
+
+ $first = count($parameters) >= 1 ? $parameters[0] : null;
+ $second = count($parameters) >= 2 ? $parameters[1] : null;
+
+ switch ($method) {
+ case 'start':
+ case 'since':
+ return $this->setStartDate($first, $second);
+
+ case 'sinceNow':
+ return $this->setStartDate(new Carbon, $first);
+
+ case 'end':
+ case 'until':
+ return $this->setEndDate($first, $second);
+
+ case 'untilNow':
+ return $this->setEndDate(new Carbon, $first);
+
+ case 'dates':
+ case 'between':
+ return $this->setDates($first, $second);
+
+ case 'recurrences':
+ case 'times':
+ return $this->setRecurrences($first);
+
+ case 'options':
+ return $this->setOptions($first);
+
+ case 'toggle':
+ return $this->toggleOptions($first, $second);
+
+ case 'filter':
+ case 'push':
+ return $this->addFilter($first, $second);
+
+ case 'prepend':
+ return $this->prependFilter($first, $second);
+
+ case 'filters':
+ return $this->setFilters($first ?: array());
+
+ case 'interval':
+ case 'each':
+ case 'every':
+ case 'step':
+ case 'stepBy':
+ return $this->setDateInterval($first);
+
+ case 'invert':
+ return $this->invertDateInterval();
+
+ case 'years':
+ case 'year':
+ case 'months':
+ case 'month':
+ case 'weeks':
+ case 'week':
+ case 'days':
+ case 'dayz':
+ case 'day':
+ case 'hours':
+ case 'hour':
+ case 'minutes':
+ case 'minute':
+ case 'seconds':
+ case 'second':
+ return $this->setDateInterval(call_user_func(
+ // Override default P1D when instantiating via fluent setters.
+ array($this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method),
+ count($parameters) === 0 ? 1 : $first
+ ));
+ }
+
+ throw new BadMethodCallException("Method $method does not exist.");
+ }
+}
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php b/core/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php
new file mode 100644
index 0000000..1b0d473
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Carbon\Exceptions;
+
+use Exception;
+use InvalidArgumentException;
+
+class InvalidDateException extends InvalidArgumentException
+{
+ /**
+ * The invalid field.
+ *
+ * @var string
+ */
+ private $field;
+
+ /**
+ * The invalid value.
+ *
+ * @var mixed
+ */
+ private $value;
+
+ /**
+ * Constructor.
+ *
+ * @param string $field
+ * @param mixed $value
+ * @param int $code
+ * @param \Exception|null $previous
+ */
+ public function __construct($field, $value, $code = 0, Exception $previous = null)
+ {
+ $this->field = $field;
+ $this->value = $value;
+ parent::__construct($field.' : '.$value.' is not a valid value.', $code, $previous);
+ }
+
+ /**
+ * Get the invalid field.
+ *
+ * @return string
+ */
+ public function getField()
+ {
+ return $this->field;
+ }
+
+ /**
+ * Get the invalid value.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+}
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/af.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/af.php
new file mode 100644
index 0000000..5cf6a8d
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/af.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count jaar|:count jare',
+ 'y' => ':count jaar|:count jare',
+ 'month' => ':count maand|:count maande',
+ 'm' => ':count maand|:count maande',
+ 'week' => ':count week|:count weke',
+ 'w' => ':count week|:count weke',
+ 'day' => ':count dag|:count dae',
+ 'd' => ':count dag|:count dae',
+ 'hour' => ':count uur|:count ure',
+ 'h' => ':count uur|:count ure',
+ 'minute' => ':count minuut|:count minute',
+ 'min' => ':count minuut|:count minute',
+ 'second' => ':count sekond|:count sekondes',
+ 's' => ':count sekond|:count sekondes',
+ 'ago' => ':time terug',
+ 'from_now' => ':time van nou af',
+ 'after' => ':time na',
+ 'before' => ':time voor',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ar.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ar.php
new file mode 100644
index 0000000..de8f6b8
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ar.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '{0}سنة|{1}سنة|{2}سنتين|[3,10]:count سنوات|[11,Inf]:count سنة',
+ 'y' => '{0}سنة|{1}سنة|{2}سنتين|[3,10]:count سنوات|[11,Inf]:count سنة',
+ 'month' => '{0}شهر|{1} شهر|{2}شهرين|[3,10]:count أشهر|[11,Inf]:count شهر',
+ 'm' => '{0}شهر|{1} شهر|{2}شهرين|[3,10]:count أشهر|[11,Inf]:count شهر',
+ 'week' => '{0}أسبوع|{1}أسبوع|{2}أسبوعين|[3,10]:count أسابيع|[11,Inf]:count أسبوع',
+ 'w' => '{0}أسبوع|{1}أسبوع|{2}أسبوعين|[3,10]:count أسابيع|[11,Inf]:count أسبوع',
+ 'day' => '{0}يوم|{1}يوم|{2}يومين|[3,10]:count أيام|[11,Inf] يوم',
+ 'd' => '{0}يوم|{1}يوم|{2}يومين|[3,10]:count أيام|[11,Inf] يوم',
+ 'hour' => '{0}ساعة|{1}ساعة|{2}ساعتين|[3,10]:count ساعات|[11,Inf]:count ساعة',
+ 'h' => '{0}ساعة|{1}ساعة|{2}ساعتين|[3,10]:count ساعات|[11,Inf]:count ساعة',
+ 'minute' => '{0}دقيقة|{1}دقيقة|{2}دقيقتين|[3,10]:count دقائق|[11,Inf]:count دقيقة',
+ 'min' => '{0}دقيقة|{1}دقيقة|{2}دقيقتين|[3,10]:count دقائق|[11,Inf]:count دقيقة',
+ 'second' => '{0}ثانية|{1}ثانية|{2}ثانيتين|[3,10]:count ثوان|[11,Inf]:count ثانية',
+ 's' => '{0}ثانية|{1}ثانية|{2}ثانيتين|[3,10]:count ثوان|[11,Inf]:count ثانية',
+ 'ago' => 'منذ :time',
+ 'from_now' => ':time من الآن',
+ 'after' => 'بعد :time',
+ 'before' => 'قبل :time',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php
new file mode 100644
index 0000000..846ae02
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '[0,1] سَنَة|{2} سَنَتَيْن|[3,10]:count سَنَوَات|[11,Inf]:count سَنَة',
+ 'y' => '[0,1] سَنَة|{2} سَنَتَيْن|[3,10]:count سَنَوَات|[11,Inf]:count سَنَة',
+ 'month' => '[0,1] شَهْرَ|{2} شَهْرَيْن|[3,10]:count أَشْهُر|[11,Inf]:count شَهْرَ',
+ 'm' => '[0,1] شَهْرَ|{2} شَهْرَيْن|[3,10]:count أَشْهُر|[11,Inf]:count شَهْرَ',
+ 'week' => '[0,1] أُسْبُوع|{2} أُسْبُوعَيْن|[3,10]:count أَسَابِيع|[11,Inf]:count أُسْبُوع',
+ 'w' => '[0,1] أُسْبُوع|{2} أُسْبُوعَيْن|[3,10]:count أَسَابِيع|[11,Inf]:count أُسْبُوع',
+ 'day' => '[0,1] يَوْم|{2} يَوْمَيْن|[3,10]:count أَيَّام|[11,Inf] يَوْم',
+ 'd' => '[0,1] يَوْم|{2} يَوْمَيْن|[3,10]:count أَيَّام|[11,Inf] يَوْم',
+ 'hour' => '[0,1] سَاعَة|{2} سَاعَتَيْن|[3,10]:count سَاعَات|[11,Inf]:count سَاعَة',
+ 'h' => '[0,1] سَاعَة|{2} سَاعَتَيْن|[3,10]:count سَاعَات|[11,Inf]:count سَاعَة',
+ 'minute' => '[0,1] دَقِيقَة|{2} دَقِيقَتَيْن|[3,10]:count دَقَائِق|[11,Inf]:count دَقِيقَة',
+ 'min' => '[0,1] دَقِيقَة|{2} دَقِيقَتَيْن|[3,10]:count دَقَائِق|[11,Inf]:count دَقِيقَة',
+ 'second' => '[0,1] ثَانِيَة|{2} ثَانِيَتَيْن|[3,10]:count ثَوَان|[11,Inf]:count ثَانِيَة',
+ 's' => '[0,1] ثَانِيَة|{2} ثَانِيَتَيْن|[3,10]:count ثَوَان|[11,Inf]:count ثَانِيَة',
+ 'ago' => 'مُنْذُ :time',
+ 'from_now' => 'مِنَ الْآن :time',
+ 'after' => 'بَعْدَ :time',
+ 'before' => 'قَبْلَ :time',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/az.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/az.php
new file mode 100644
index 0000000..25f5c4a
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/az.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count il',
+ 'y' => ':count il',
+ 'month' => ':count ay',
+ 'm' => ':count ay',
+ 'week' => ':count həftə',
+ 'w' => ':count həftə',
+ 'day' => ':count gün',
+ 'd' => ':count gün',
+ 'hour' => ':count saat',
+ 'h' => ':count saat',
+ 'minute' => ':count dəqiqə',
+ 'min' => ':count dəqiqə',
+ 'second' => ':count saniyə',
+ 's' => ':count saniyə',
+ 'ago' => ':time əvvəl',
+ 'from_now' => ':time sonra',
+ 'after' => ':time sonra',
+ 'before' => ':time əvvəl',
+ 'diff_now' => 'indi',
+ 'diff_yesterday' => 'dünən',
+ 'diff_tomorrow' => 'sabah',
+ 'diff_before_yesterday' => 'srağagün',
+ 'diff_after_tomorrow' => 'birisi gün',
+ 'period_recurrences' => ':count dəfədən bir',
+ 'period_interval' => 'hər :interval',
+ 'period_start_date' => ':date tarixindən başlayaraq',
+ 'period_end_date' => ':date tarixinədək',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/bg.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/bg.php
new file mode 100644
index 0000000..d9e510b
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/bg.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count година|:count години',
+ 'y' => ':count година|:count години',
+ 'month' => ':count месец|:count месеца',
+ 'm' => ':count месец|:count месеца',
+ 'week' => ':count седмица|:count седмици',
+ 'w' => ':count седмица|:count седмици',
+ 'day' => ':count ден|:count дни',
+ 'd' => ':count ден|:count дни',
+ 'hour' => ':count час|:count часа',
+ 'h' => ':count час|:count часа',
+ 'minute' => ':count минута|:count минути',
+ 'min' => ':count минута|:count минути',
+ 'second' => ':count секунда|:count секунди',
+ 's' => ':count секунда|:count секунди',
+ 'ago' => 'преди :time',
+ 'from_now' => ':time от сега',
+ 'after' => 'след :time',
+ 'before' => 'преди :time',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/bn.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/bn.php
new file mode 100644
index 0000000..5817599
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/bn.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '১ বছর|:count বছর',
+ 'y' => '১ বছর|:count বছর',
+ 'month' => '১ মাস|:count মাস',
+ 'm' => '১ মাস|:count মাস',
+ 'week' => '১ সপ্তাহ|:count সপ্তাহ',
+ 'w' => '১ সপ্তাহ|:count সপ্তাহ',
+ 'day' => '১ দিন|:count দিন',
+ 'd' => '১ দিন|:count দিন',
+ 'hour' => '১ ঘন্টা|:count ঘন্টা',
+ 'h' => '১ ঘন্টা|:count ঘন্টা',
+ 'minute' => '১ মিনিট|:count মিনিট',
+ 'min' => '১ মিনিট|:count মিনিট',
+ 'second' => '১ সেকেন্ড|:count সেকেন্ড',
+ 's' => '১ সেকেন্ড|:count সেকেন্ড',
+ 'ago' => ':time পূর্বে',
+ 'from_now' => 'এখন থেকে :time',
+ 'after' => ':time পরে',
+ 'before' => ':time আগে',
+ 'diff_now' => 'এখন',
+ 'diff_yesterday' => 'গতকাল',
+ 'diff_tomorrow' => 'আগামীকাল',
+ 'period_recurrences' => ':count বার|:count বার',
+ 'period_interval' => 'প্রতি :interval',
+ 'period_start_date' => ':date থেকে',
+ 'period_end_date' => ':date পর্যন্ত',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php
new file mode 100644
index 0000000..7a9b05a
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count godina|:count godine|:count godina',
+ 'y' => ':count godina|:count godine|:count godina',
+ 'month' => ':count mjesec|:count mjeseca|:count mjeseci',
+ 'm' => ':count mjesec|:count mjeseca|:count mjeseci',
+ 'week' => ':count nedjelja|:count nedjelje|:count nedjelja',
+ 'w' => ':count nedjelja|:count nedjelje|:count nedjelja',
+ 'day' => ':count dan|:count dana|:count dana',
+ 'd' => ':count dan|:count dana|:count dana',
+ 'hour' => ':count sat|:count sata|:count sati',
+ 'h' => ':count sat|:count sata|:count sati',
+ 'minute' => ':count minut|:count minuta|:count minuta',
+ 'min' => ':count minut|:count minuta|:count minuta',
+ 'second' => ':count sekund|:count sekunda|:count sekundi',
+ 's' => ':count sekund|:count sekunda|:count sekundi',
+ 'ago' => 'prije :time',
+ 'from_now' => 'za :time',
+ 'after' => 'nakon :time',
+ 'before' => ':time ranije',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ca.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ca.php
new file mode 100644
index 0000000..c854b5a
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ca.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count any|:count anys',
+ 'y' => ':count any|:count anys',
+ 'month' => ':count mes|:count mesos',
+ 'm' => ':count mes|:count mesos',
+ 'week' => ':count setmana|:count setmanes',
+ 'w' => ':count setmana|:count setmanes',
+ 'day' => ':count dia|:count dies',
+ 'd' => ':count dia|:count dies',
+ 'hour' => ':count hora|:count hores',
+ 'h' => ':count hora|:count hores',
+ 'minute' => ':count minut|:count minuts',
+ 'min' => ':count minut|:count minuts',
+ 'second' => ':count segon|:count segons',
+ 's' => ':count segon|:count segons',
+ 'ago' => 'fa :time',
+ 'from_now' => 'd\'aquí :time',
+ 'after' => ':time després',
+ 'before' => ':time abans',
+ 'diff_now' => 'ara mateix',
+ 'diff_yesterday' => 'ahir',
+ 'diff_tomorrow' => 'demà',
+ 'diff_before_yesterday' => "abans d'ahir",
+ 'diff_after_tomorrow' => 'demà passat',
+ 'period_recurrences' => ':count cop|:count cops',
+ 'period_interval' => 'cada :interval',
+ 'period_start_date' => 'de :date',
+ 'period_end_date' => 'fins a :date',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/cs.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/cs.php
new file mode 100644
index 0000000..a447ce2
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/cs.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count rok|:count roky|:count let',
+ 'y' => ':count rok|:count roky|:count let',
+ 'month' => ':count měsíc|:count měsíce|:count měsíců',
+ 'm' => ':count měsíc|:count měsíce|:count měsíců',
+ 'week' => ':count týden|:count týdny|:count týdnů',
+ 'w' => ':count týden|:count týdny|:count týdnů',
+ 'day' => ':count den|:count dny|:count dní',
+ 'd' => ':count den|:count dny|:count dní',
+ 'hour' => ':count hodinu|:count hodiny|:count hodin',
+ 'h' => ':count hodinu|:count hodiny|:count hodin',
+ 'minute' => ':count minutu|:count minuty|:count minut',
+ 'min' => ':count minutu|:count minuty|:count minut',
+ 'second' => ':count sekundu|:count sekundy|:count sekund',
+ 's' => ':count sekundu|:count sekundy|:count sekund',
+ 'ago' => ':time nazpět',
+ 'from_now' => 'za :time',
+ 'after' => ':time později',
+ 'before' => ':time předtím',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/cy.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/cy.php
new file mode 100644
index 0000000..c93750e
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/cy.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+return array(
+ 'year' => '1 flwyddyn|:count blynedd',
+ 'y' => ':countbl',
+ 'month' => '1 mis|:count fis',
+ 'm' => ':countmi',
+ 'week' => ':count wythnos',
+ 'w' => ':countw',
+ 'day' => ':count diwrnod',
+ 'd' => ':countd',
+ 'hour' => ':count awr',
+ 'h' => ':counth',
+ 'minute' => ':count munud',
+ 'min' => ':countm',
+ 'second' => ':count eiliad',
+ 's' => ':counts',
+ 'ago' => ':time yn ôl',
+ 'from_now' => ':time o hyn ymlaen',
+ 'after' => ':time ar ôl',
+ 'before' => ':time o\'r blaen',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/da.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/da.php
new file mode 100644
index 0000000..86507b0
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/da.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count år|:count år',
+ 'y' => ':count år|:count år',
+ 'month' => ':count måned|:count måneder',
+ 'm' => ':count måned|:count måneder',
+ 'week' => ':count uge|:count uger',
+ 'w' => ':count uge|:count uger',
+ 'day' => ':count dag|:count dage',
+ 'd' => ':count dag|:count dage',
+ 'hour' => ':count time|:count timer',
+ 'h' => ':count time|:count timer',
+ 'minute' => ':count minut|:count minutter',
+ 'min' => ':count minut|:count minutter',
+ 'second' => ':count sekund|:count sekunder',
+ 's' => ':count sekund|:count sekunder',
+ 'ago' => ':time siden',
+ 'from_now' => 'om :time',
+ 'after' => ':time efter',
+ 'before' => ':time før',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/de.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/de.php
new file mode 100644
index 0000000..5ea2a03
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/de.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count Jahr|:count Jahre',
+ 'y' => ':countJ|:countJ',
+ 'month' => ':count Monat|:count Monate',
+ 'm' => ':countMon|:countMon',
+ 'week' => ':count Woche|:count Wochen',
+ 'w' => ':countWo|:countWo',
+ 'day' => ':count Tag|:count Tage',
+ 'd' => ':countTg|:countTg',
+ 'hour' => ':count Stunde|:count Stunden',
+ 'h' => ':countStd|:countStd',
+ 'minute' => ':count Minute|:count Minuten',
+ 'min' => ':countMin|:countMin',
+ 'second' => ':count Sekunde|:count Sekunden',
+ 's' => ':countSek|:countSek',
+ 'ago' => 'vor :time',
+ 'from_now' => 'in :time',
+ 'after' => ':time später',
+ 'before' => ':time zuvor',
+
+ 'year_from_now' => ':count Jahr|:count Jahren',
+ 'month_from_now' => ':count Monat|:count Monaten',
+ 'week_from_now' => ':count Woche|:count Wochen',
+ 'day_from_now' => ':count Tag|:count Tagen',
+ 'year_ago' => ':count Jahr|:count Jahren',
+ 'month_ago' => ':count Monat|:count Monaten',
+ 'week_ago' => ':count Woche|:count Wochen',
+ 'day_ago' => ':count Tag|:count Tagen',
+
+ 'diff_now' => 'Gerade eben',
+ 'diff_yesterday' => 'Gestern',
+ 'diff_tomorrow' => 'Heute',
+ 'diff_before_yesterday' => 'Vorgestern',
+ 'diff_after_tomorrow' => 'Übermorgen',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php
new file mode 100644
index 0000000..e3c50b3
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '{0}އަހަރެއް|[1,Inf]:count އަހަރު',
+ 'y' => '{0}އަހަރެއް|[1,Inf]:count އަހަރު',
+ 'month' => '{0}މައްސަރެއް|[1,Inf]:count މަސް',
+ 'm' => '{0}މައްސަރެއް|[1,Inf]:count މަސް',
+ 'week' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ',
+ 'w' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ',
+ 'day' => '{0}ދުވަސް|[1,Inf]:count ދުވަސް',
+ 'd' => '{0}ދުވަސް|[1,Inf]:count ދުވަސް',
+ 'hour' => '{0}ގަޑިއިރެއް|[1,Inf]:count ގަޑި',
+ 'h' => '{0}ގަޑިއިރެއް|[1,Inf]:count ގަޑި',
+ 'minute' => '{0}މިނެޓެއް|[1,Inf]:count މިނެޓް',
+ 'min' => '{0}މިނެޓެއް|[1,Inf]:count މިނެޓް',
+ 'second' => '{0}ސިކުންތެއް|[1,Inf]:count ސިކުންތު',
+ 's' => '{0}ސިކުންތެއް|[1,Inf]:count ސިކުންތު',
+ 'ago' => ':time ކުރިން',
+ 'from_now' => ':time ފަހުން',
+ 'after' => ':time ފަހުން',
+ 'before' => ':time ކުރި',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/el.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/el.php
new file mode 100644
index 0000000..16b3f44
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/el.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count χρόνος|:count χρόνια',
+ 'y' => ':count χρόνος|:count χρόνια',
+ 'month' => ':count μήνας|:count μήνες',
+ 'm' => ':count μήνας|:count μήνες',
+ 'week' => ':count εβδομάδα|:count εβδομάδες',
+ 'w' => ':count εβδομάδα|:count εβδομάδες',
+ 'day' => ':count μέρα|:count μέρες',
+ 'd' => ':count μέρα|:count μέρες',
+ 'hour' => ':count ώρα|:count ώρες',
+ 'h' => ':count ώρα|:count ώρες',
+ 'minute' => ':count λεπτό|:count λεπτά',
+ 'min' => ':count λεπτό|:count λεπτά',
+ 'second' => ':count δευτερόλεπτο|:count δευτερόλεπτα',
+ 's' => ':count δευτερόλεπτο|:count δευτερόλεπτα',
+ 'ago' => 'πριν από :time',
+ 'from_now' => 'σε :time από τώρα',
+ 'after' => ':time μετά',
+ 'before' => ':time πριν',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/en.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/en.php
new file mode 100644
index 0000000..a15c131
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/en.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count year|:count years',
+ 'y' => ':countyr|:countyrs',
+ 'month' => ':count month|:count months',
+ 'm' => ':countmo|:countmos',
+ 'week' => ':count week|:count weeks',
+ 'w' => ':countw|:countw',
+ 'day' => ':count day|:count days',
+ 'd' => ':countd|:countd',
+ 'hour' => ':count hour|:count hours',
+ 'h' => ':counth|:counth',
+ 'minute' => ':count minute|:count minutes',
+ 'min' => ':countm|:countm',
+ 'second' => ':count second|:count seconds',
+ 's' => ':counts|:counts',
+ 'ago' => ':time ago',
+ 'from_now' => ':time from now',
+ 'after' => ':time after',
+ 'before' => ':time before',
+ 'diff_now' => 'just now',
+ 'diff_yesterday' => 'yesterday',
+ 'diff_tomorrow' => 'tomorrow',
+ 'diff_before_yesterday' => 'before yesterday',
+ 'diff_after_tomorrow' => 'after tomorrow',
+ 'period_recurrences' => 'once|:count times',
+ 'period_interval' => 'every :interval',
+ 'period_start_date' => 'from :date',
+ 'period_end_date' => 'to :date',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/eo.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/eo.php
new file mode 100644
index 0000000..c5b90b3
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/eo.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count jaro|:count jaroj',
+ 'y' => ':count jaro|:count jaroj',
+ 'month' => ':count monato|:count monatoj',
+ 'm' => ':count monato|:count monatoj',
+ 'week' => ':count semajno|:count semajnoj',
+ 'w' => ':count semajno|:count semajnoj',
+ 'day' => ':count tago|:count tagoj',
+ 'd' => ':count tago|:count tagoj',
+ 'hour' => ':count horo|:count horoj',
+ 'h' => ':count horo|:count horoj',
+ 'minute' => ':count minuto|:count minutoj',
+ 'min' => ':count minuto|:count minutoj',
+ 'second' => ':count sekundo|:count sekundoj',
+ 's' => ':count sekundo|:count sekundoj',
+ 'ago' => 'antaŭ :time',
+ 'from_now' => 'je :time',
+ 'after' => ':time poste',
+ 'before' => ':time antaŭe',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/es.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/es.php
new file mode 100644
index 0000000..567a280
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/es.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count año|:count años',
+ 'y' => ':count año|:count años',
+ 'month' => ':count mes|:count meses',
+ 'm' => ':count mes|:count meses',
+ 'week' => ':count semana|:count semanas',
+ 'w' => ':count semana|:count semanas',
+ 'day' => ':count día|:count días',
+ 'd' => ':count día|:count días',
+ 'hour' => ':count hora|:count horas',
+ 'h' => ':count hora|:count horas',
+ 'minute' => ':count minuto|:count minutos',
+ 'min' => ':count minuto|:count minutos',
+ 'second' => ':count segundo|:count segundos',
+ 's' => ':count segundo|:count segundos',
+ 'ago' => 'hace :time',
+ 'from_now' => 'dentro de :time',
+ 'after' => ':time después',
+ 'before' => ':time antes',
+ 'diff_now' => 'ahora mismo',
+ 'diff_yesterday' => 'ayer',
+ 'diff_tomorrow' => 'mañana',
+ 'diff_before_yesterday' => 'antier',
+ 'diff_after_tomorrow' => 'pasado mañana',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/et.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/et.php
new file mode 100644
index 0000000..2d9291e
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/et.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count aasta|:count aastat',
+ 'y' => ':count aasta|:count aastat',
+ 'month' => ':count kuu|:count kuud',
+ 'm' => ':count kuu|:count kuud',
+ 'week' => ':count nädal|:count nädalat',
+ 'w' => ':count nädal|:count nädalat',
+ 'day' => ':count päev|:count päeva',
+ 'd' => ':count päev|:count päeva',
+ 'hour' => ':count tund|:count tundi',
+ 'h' => ':count tund|:count tundi',
+ 'minute' => ':count minut|:count minutit',
+ 'min' => ':count minut|:count minutit',
+ 'second' => ':count sekund|:count sekundit',
+ 's' => ':count sekund|:count sekundit',
+ 'ago' => ':time tagasi',
+ 'from_now' => ':time pärast',
+ 'after' => ':time pärast',
+ 'before' => ':time enne',
+ 'year_from_now' => ':count aasta',
+ 'month_from_now' => ':count kuu',
+ 'week_from_now' => ':count nädala',
+ 'day_from_now' => ':count päeva',
+ 'hour_from_now' => ':count tunni',
+ 'minute_from_now' => ':count minuti',
+ 'second_from_now' => ':count sekundi',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/eu.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/eu.php
new file mode 100644
index 0000000..1cb6b7c
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/eu.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => 'Urte 1|:count urte',
+ 'y' => 'Urte 1|:count urte',
+ 'month' => 'Hile 1|:count hile',
+ 'm' => 'Hile 1|:count hile',
+ 'week' => 'Aste 1|:count aste',
+ 'w' => 'Aste 1|:count aste',
+ 'day' => 'Egun 1|:count egun',
+ 'd' => 'Egun 1|:count egun',
+ 'hour' => 'Ordu 1|:count ordu',
+ 'h' => 'Ordu 1|:count ordu',
+ 'minute' => 'Minutu 1|:count minutu',
+ 'min' => 'Minutu 1|:count minutu',
+ 'second' => 'Segundu 1|:count segundu',
+ 's' => 'Segundu 1|:count segundu',
+ 'ago' => 'Orain dela :time',
+ 'from_now' => ':time barru',
+ 'after' => ':time geroago',
+ 'before' => ':time lehenago',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/fa.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/fa.php
new file mode 100644
index 0000000..31bec88
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/fa.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count سال',
+ 'y' => ':count سال',
+ 'month' => ':count ماه',
+ 'm' => ':count ماه',
+ 'week' => ':count هفته',
+ 'w' => ':count هفته',
+ 'day' => ':count روز',
+ 'd' => ':count روز',
+ 'hour' => ':count ساعت',
+ 'h' => ':count ساعت',
+ 'minute' => ':count دقیقه',
+ 'min' => ':count دقیقه',
+ 'second' => ':count ثانیه',
+ 's' => ':count ثانیه',
+ 'ago' => ':time پیش',
+ 'from_now' => ':time بعد',
+ 'after' => ':time پس از',
+ 'before' => ':time پیش از',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/fi.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/fi.php
new file mode 100644
index 0000000..4818804
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/fi.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count vuosi|:count vuotta',
+ 'y' => ':count vuosi|:count vuotta',
+ 'month' => ':count kuukausi|:count kuukautta',
+ 'm' => ':count kuukausi|:count kuukautta',
+ 'week' => ':count viikko|:count viikkoa',
+ 'w' => ':count viikko|:count viikkoa',
+ 'day' => ':count päivä|:count päivää',
+ 'd' => ':count päivä|:count päivää',
+ 'hour' => ':count tunti|:count tuntia',
+ 'h' => ':count tunti|:count tuntia',
+ 'minute' => ':count minuutti|:count minuuttia',
+ 'min' => ':count minuutti|:count minuuttia',
+ 'second' => ':count sekunti|:count sekuntia',
+ 's' => ':count sekunti|:count sekuntia',
+ 'ago' => ':time sitten',
+ 'from_now' => ':time tästä hetkestä',
+ 'after' => ':time sen jälkeen',
+ 'before' => ':time ennen',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/fo.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/fo.php
new file mode 100644
index 0000000..d91104b
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/fo.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count ár|:count ár',
+ 'y' => ':count ár|:count ár',
+ 'month' => ':count mánaður|:count mánaðir',
+ 'm' => ':count mánaður|:count mánaðir',
+ 'week' => ':count vika|:count vikur',
+ 'w' => ':count vika|:count vikur',
+ 'day' => ':count dag|:count dagar',
+ 'd' => ':count dag|:count dagar',
+ 'hour' => ':count tími|:count tímar',
+ 'h' => ':count tími|:count tímar',
+ 'minute' => ':count minutt|:count minuttir',
+ 'min' => ':count minutt|:count minuttir',
+ 'second' => ':count sekund|:count sekundir',
+ 's' => ':count sekund|:count sekundir',
+ 'ago' => ':time síðan',
+ 'from_now' => 'um :time',
+ 'after' => ':time aftaná',
+ 'before' => ':time áðrenn',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/fr.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/fr.php
new file mode 100644
index 0000000..0b20cdd
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/fr.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count an|:count ans',
+ 'y' => ':count an|:count ans',
+ 'month' => ':count mois',
+ 'm' => ':count mois',
+ 'week' => ':count semaine|:count semaines',
+ 'w' => ':count sem.',
+ 'day' => ':count jour|:count jours',
+ 'd' => ':count j.',
+ 'hour' => ':count heure|:count heures',
+ 'h' => ':count h.',
+ 'minute' => ':count minute|:count minutes',
+ 'min' => ':count min.',
+ 'second' => ':count seconde|:count secondes',
+ 's' => ':count sec.',
+ 'ago' => 'il y a :time',
+ 'from_now' => 'dans :time',
+ 'after' => ':time après',
+ 'before' => ':time avant',
+ 'diff_now' => "à l'instant",
+ 'diff_yesterday' => 'hier',
+ 'diff_tomorrow' => 'demain',
+ 'diff_before_yesterday' => 'avant-hier',
+ 'diff_after_tomorrow' => 'après-demain',
+ 'period_recurrences' => ':count fois',
+ 'period_interval' => 'tous les :interval',
+ 'period_start_date' => 'de :date',
+ 'period_end_date' => 'à :date',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/gl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/gl.php
new file mode 100644
index 0000000..cd22a31
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/gl.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count ano|:count anos',
+ 'month' => ':count mes|:count meses',
+ 'week' => ':count semana|:count semanas',
+ 'day' => ':count día|:count días',
+ 'hour' => ':count hora|:count horas',
+ 'minute' => ':count minuto|:count minutos',
+ 'second' => ':count segundo|:count segundos',
+ 'ago' => 'fai :time',
+ 'from_now' => 'dentro de :time',
+ 'after' => ':time despois',
+ 'before' => ':time antes',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/gu.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/gu.php
new file mode 100644
index 0000000..7759dfc
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/gu.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count વર્ષ|:count વર્ષો',
+ 'y' => ':countવર્ષ|:countવર્ષો',
+ 'month' => ':count મહિનો|:count મહિના',
+ 'm' => ':countમહિનો|:countમહિના',
+ 'week' => ':count અઠવાડિયું|:count અઠવાડિયા',
+ 'w' => ':countઅઠ.|:countઅઠ.',
+ 'day' => ':count દિવસ|:count દિવસો',
+ 'd' => ':countદિ.|:countદિ.',
+ 'hour' => ':count કલાક|:count કલાકો',
+ 'h' => ':countક.|:countક.',
+ 'minute' => ':count મિનિટ|:count મિનિટ',
+ 'min' => ':countમિ.|:countમિ.',
+ 'second' => ':count સેકેન્ડ|:count સેકેન્ડ',
+ 's' => ':countસે.|:countસે.',
+ 'ago' => ':time પહેલા',
+ 'from_now' => ':time અત્યારથી',
+ 'after' => ':time પછી',
+ 'before' => ':time પહેલા',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/he.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/he.php
new file mode 100644
index 0000000..2d4f4f8
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/he.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => 'שנה|{2}שנתיים|:count שנים',
+ 'y' => 'שנה|{2}שנתיים|:count שנים',
+ 'month' => 'חודש|{2}חודשיים|:count חודשים',
+ 'm' => 'חודש|{2}חודשיים|:count חודשים',
+ 'week' => 'שבוע|{2}שבועיים|:count שבועות',
+ 'w' => 'שבוע|{2}שבועיים|:count שבועות',
+ 'day' => 'יום|{2}יומיים|:count ימים',
+ 'd' => 'יום|{2}יומיים|:count ימים',
+ 'hour' => 'שעה|{2}שעתיים|:count שעות',
+ 'h' => 'שעה|{2}שעתיים|:count שעות',
+ 'minute' => 'דקה|{2}דקותיים|:count דקות',
+ 'min' => 'דקה|{2}דקותיים|:count דקות',
+ 'second' => 'שניה|:count שניות',
+ 's' => 'שניה|:count שניות',
+ 'ago' => 'לפני :time',
+ 'from_now' => 'בעוד :time',
+ 'after' => 'אחרי :time',
+ 'before' => 'לפני :time',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/hi.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/hi.php
new file mode 100644
index 0000000..6c670ee
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/hi.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '1 वर्ष|:count वर्षों',
+ 'y' => '1 वर्ष|:count वर्षों',
+ 'month' => '1 माह|:count महीने',
+ 'm' => '1 माह|:count महीने',
+ 'week' => '1 सप्ताह|:count सप्ताह',
+ 'w' => '1 सप्ताह|:count सप्ताह',
+ 'day' => '1 दिन|:count दिनों',
+ 'd' => '1 दिन|:count दिनों',
+ 'hour' => '1 घंटा|:count घंटे',
+ 'h' => '1 घंटा|:count घंटे',
+ 'minute' => '1 मिनट|:count मिनटों',
+ 'min' => '1 मिनट|:count मिनटों',
+ 'second' => '1 सेकंड|:count सेकंड',
+ 's' => '1 सेकंड|:count सेकंड',
+ 'ago' => ':time पूर्व',
+ 'from_now' => ':time से',
+ 'after' => ':time के बाद',
+ 'before' => ':time के पहले',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/hr.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/hr.php
new file mode 100644
index 0000000..1a339de
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/hr.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count godinu|:count godine|:count godina',
+ 'y' => ':count godinu|:count godine|:count godina',
+ 'month' => ':count mjesec|:count mjeseca|:count mjeseci',
+ 'm' => ':count mjesec|:count mjeseca|:count mjeseci',
+ 'week' => ':count tjedan|:count tjedna|:count tjedana',
+ 'w' => ':count tjedan|:count tjedna|:count tjedana',
+ 'day' => ':count dan|:count dana|:count dana',
+ 'd' => ':count dan|:count dana|:count dana',
+ 'hour' => ':count sat|:count sata|:count sati',
+ 'h' => ':count sat|:count sata|:count sati',
+ 'minute' => ':count minutu|:count minute |:count minuta',
+ 'min' => ':count minutu|:count minute |:count minuta',
+ 'second' => ':count sekundu|:count sekunde|:count sekundi',
+ 's' => ':count sekundu|:count sekunde|:count sekundi',
+ 'ago' => 'prije :time',
+ 'from_now' => 'za :time',
+ 'after' => 'za :time',
+ 'before' => 'prije :time',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/hu.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/hu.php
new file mode 100644
index 0000000..45daf41
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/hu.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count év',
+ 'y' => ':count év',
+ 'month' => ':count hónap',
+ 'm' => ':count hónap',
+ 'week' => ':count hét',
+ 'w' => ':count hét',
+ 'day' => ':count nap',
+ 'd' => ':count nap',
+ 'hour' => ':count óra',
+ 'h' => ':count óra',
+ 'minute' => ':count perc',
+ 'min' => ':count perc',
+ 'second' => ':count másodperc',
+ 's' => ':count másodperc',
+ 'ago' => ':time',
+ 'from_now' => ':time múlva',
+ 'after' => ':time később',
+ 'before' => ':time korábban',
+ 'year_ago' => ':count éve',
+ 'month_ago' => ':count hónapja',
+ 'week_ago' => ':count hete',
+ 'day_ago' => ':count napja',
+ 'hour_ago' => ':count órája',
+ 'minute_ago' => ':count perce',
+ 'second_ago' => ':count másodperce',
+ 'year_after' => ':count évvel',
+ 'month_after' => ':count hónappal',
+ 'week_after' => ':count héttel',
+ 'day_after' => ':count nappal',
+ 'hour_after' => ':count órával',
+ 'minute_after' => ':count perccel',
+ 'second_after' => ':count másodperccel',
+ 'year_before' => ':count évvel',
+ 'month_before' => ':count hónappal',
+ 'week_before' => ':count héttel',
+ 'day_before' => ':count nappal',
+ 'hour_before' => ':count órával',
+ 'minute_before' => ':count perccel',
+ 'second_before' => ':count másodperccel',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/hy.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/hy.php
new file mode 100644
index 0000000..d2665f2
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/hy.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count տարի',
+ 'y' => ':countտ',
+ 'month' => ':count ամիս',
+ 'm' => ':countամ',
+ 'week' => ':count շաբաթ',
+ 'w' => ':countշ',
+ 'day' => ':count օր',
+ 'd' => ':countօր',
+ 'hour' => ':count ժամ',
+ 'h' => ':countժ',
+ 'minute' => ':count րոպե',
+ 'min' => ':countր',
+ 'second' => ':count վարկյան',
+ 's' => ':countվրկ',
+ 'ago' => ':time առաջ',
+ 'from_now' => ':time ներկա պահից',
+ 'after' => ':time հետո',
+ 'before' => ':time առաջ',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/id.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/id.php
new file mode 100644
index 0000000..7f7114f
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/id.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count tahun',
+ 'y' => ':count tahun',
+ 'month' => ':count bulan',
+ 'm' => ':count bulan',
+ 'week' => ':count minggu',
+ 'w' => ':count minggu',
+ 'day' => ':count hari',
+ 'd' => ':count hari',
+ 'hour' => ':count jam',
+ 'h' => ':count jam',
+ 'minute' => ':count menit',
+ 'min' => ':count menit',
+ 'second' => ':count detik',
+ 's' => ':count detik',
+ 'ago' => ':time yang lalu',
+ 'from_now' => ':time dari sekarang',
+ 'after' => ':time setelah',
+ 'before' => ':time sebelum',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/is.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/is.php
new file mode 100644
index 0000000..94c76a7
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/is.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '1 ár|:count ár',
+ 'y' => '1 ár|:count ár',
+ 'month' => '1 mánuður|:count mánuðir',
+ 'm' => '1 mánuður|:count mánuðir',
+ 'week' => '1 vika|:count vikur',
+ 'w' => '1 vika|:count vikur',
+ 'day' => '1 dagur|:count dagar',
+ 'd' => '1 dagur|:count dagar',
+ 'hour' => '1 klukkutími|:count klukkutímar',
+ 'h' => '1 klukkutími|:count klukkutímar',
+ 'minute' => '1 mínúta|:count mínútur',
+ 'min' => '1 mínúta|:count mínútur',
+ 'second' => '1 sekúnda|:count sekúndur',
+ 's' => '1 sekúnda|:count sekúndur',
+ 'ago' => ':time síðan',
+ 'from_now' => ':time síðan',
+ 'after' => ':time eftir',
+ 'before' => ':time fyrir',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/it.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/it.php
new file mode 100644
index 0000000..70bc6d7
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/it.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count anno|:count anni',
+ 'y' => ':count anno|:count anni',
+ 'month' => ':count mese|:count mesi',
+ 'm' => ':count mese|:count mesi',
+ 'week' => ':count settimana|:count settimane',
+ 'w' => ':count settimana|:count settimane',
+ 'day' => ':count giorno|:count giorni',
+ 'd' => ':count giorno|:count giorni',
+ 'hour' => ':count ora|:count ore',
+ 'h' => ':count ora|:count ore',
+ 'minute' => ':count minuto|:count minuti',
+ 'min' => ':count minuto|:count minuti',
+ 'second' => ':count secondo|:count secondi',
+ 's' => ':count secondo|:count secondi',
+ 'ago' => ':time fa',
+ 'from_now' => 'tra :time',
+ 'after' => ':time dopo',
+ 'before' => ':time prima',
+ 'diff_now' => 'proprio ora',
+ 'diff_yesterday' => 'ieri',
+ 'diff_tomorrow' => 'domani',
+ 'diff_before_yesterday' => "l'altro ieri",
+ 'diff_after_tomorrow' => 'dopodomani',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ja.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ja.php
new file mode 100644
index 0000000..7119547
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ja.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count年',
+ 'y' => ':count年',
+ 'month' => ':countヶ月',
+ 'm' => ':countヶ月',
+ 'week' => ':count週間',
+ 'w' => ':count週間',
+ 'day' => ':count日',
+ 'd' => ':count日',
+ 'hour' => ':count時間',
+ 'h' => ':count時間',
+ 'minute' => ':count分',
+ 'min' => ':count分',
+ 'second' => ':count秒',
+ 's' => ':count秒',
+ 'ago' => ':time前',
+ 'from_now' => '今から:time',
+ 'after' => ':time後',
+ 'before' => ':time前',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ka.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ka.php
new file mode 100644
index 0000000..a8dde7e
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ka.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count წლის',
+ 'y' => ':count წლის',
+ 'month' => ':count თვის',
+ 'm' => ':count თვის',
+ 'week' => ':count კვირის',
+ 'w' => ':count კვირის',
+ 'day' => ':count დღის',
+ 'd' => ':count დღის',
+ 'hour' => ':count საათის',
+ 'h' => ':count საათის',
+ 'minute' => ':count წუთის',
+ 'min' => ':count წუთის',
+ 'second' => ':count წამის',
+ 's' => ':count წამის',
+ 'ago' => ':time უკან',
+ 'from_now' => ':time შემდეგ',
+ 'after' => ':time შემდეგ',
+ 'before' => ':time უკან',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/kk.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/kk.php
new file mode 100644
index 0000000..8d113af
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/kk.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+return array(
+ 'year' => ':count жыл',
+ 'y' => ':count жыл',
+ 'month' => ':count ай',
+ 'm' => ':count ай',
+ 'week' => ':count апта',
+ 'w' => ':count апта',
+ 'day' => ':count күн',
+ 'd' => ':count күн',
+ 'hour' => ':count сағат',
+ 'h' => ':count сағат',
+ 'minute' => ':count минут',
+ 'min' => ':count минут',
+ 'second' => ':count секунд',
+ 's' => ':count секунд',
+ 'ago' => ':time бұрын',
+ 'from_now' => ':time кейін',
+ 'after' => ':time кейін',
+ 'before' => ':time бұрын',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/km.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/km.php
new file mode 100644
index 0000000..a104e06
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/km.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count ឆ្នាំ',
+ 'y' => ':count ឆ្នាំ',
+ 'month' => ':count ខែ',
+ 'm' => ':count ខែ',
+ 'week' => ':count សប្ដាហ៍',
+ 'w' => ':count សប្ដាហ៍',
+ 'day' => ':count ថ្ងៃ',
+ 'd' => ':count ថ្ងៃ',
+ 'hour' => ':count ម៉ោង',
+ 'h' => ':count ម៉ោង',
+ 'minute' => ':count នាទី',
+ 'min' => ':count នាទី',
+ 'second' => ':count វិនាទី',
+ 's' => ':count វិនាទី',
+ 'ago' => ':timeមុន',
+ 'from_now' => ':timeពីឥឡូវ',
+ 'after' => 'នៅក្រោយ :time',
+ 'before' => 'នៅមុន :time',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ko.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ko.php
new file mode 100644
index 0000000..0209164
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ko.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count 년',
+ 'y' => ':count 년',
+ 'month' => ':count 개월',
+ 'm' => ':count 개월',
+ 'week' => ':count 주일',
+ 'w' => ':count 주일',
+ 'day' => ':count 일',
+ 'd' => ':count 일',
+ 'hour' => ':count 시간',
+ 'h' => ':count 시간',
+ 'minute' => ':count 분',
+ 'min' => ':count 분',
+ 'second' => ':count 초',
+ 's' => ':count 초',
+ 'ago' => ':time 전',
+ 'from_now' => ':time 후',
+ 'after' => ':time 이후',
+ 'before' => ':time 이전',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/lt.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/lt.php
new file mode 100644
index 0000000..3f2fd1e
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/lt.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count metus|:count metus|:count metų',
+ 'y' => ':count metus|:count metus|:count metų',
+ 'month' => ':count mėnesį|:count mėnesius|:count mėnesių',
+ 'm' => ':count mėnesį|:count mėnesius|:count mėnesių',
+ 'week' => ':count savaitę|:count savaites|:count savaičių',
+ 'w' => ':count savaitę|:count savaites|:count savaičių',
+ 'day' => ':count dieną|:count dienas|:count dienų',
+ 'd' => ':count dieną|:count dienas|:count dienų',
+ 'hour' => ':count valandą|:count valandas|:count valandų',
+ 'h' => ':count valandą|:count valandas|:count valandų',
+ 'minute' => ':count minutę|:count minutes|:count minučių',
+ 'min' => ':count minutę|:count minutes|:count minučių',
+ 'second' => ':count sekundę|:count sekundes|:count sekundžių',
+ 's' => ':count sekundę|:count sekundes|:count sekundžių',
+ 'second_from_now' => ':count sekundės|:count sekundžių|:count sekundžių',
+ 'minute_from_now' => ':count minutės|:count minučių|:count minučių',
+ 'hour_from_now' => ':count valandos|:count valandų|:count valandų',
+ 'day_from_now' => ':count dienos|:count dienų|:count dienų',
+ 'week_from_now' => ':count savaitės|:count savaičių|:count savaičių',
+ 'month_from_now' => ':count mėnesio|:count mėnesių|:count mėnesių',
+ 'year_from_now' => ':count metų',
+ 'ago' => 'prieš :time',
+ 'from_now' => 'už :time',
+ 'after' => 'po :time',
+ 'before' => ':time nuo dabar',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/lv.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/lv.php
new file mode 100644
index 0000000..363193d
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/lv.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '0 gadiem|:count gada|:count gadiem',
+ 'y' => '0 gadiem|:count gada|:count gadiem',
+ 'month' => '0 mēnešiem|:count mēneša|:count mēnešiem',
+ 'm' => '0 mēnešiem|:count mēneša|:count mēnešiem',
+ 'week' => '0 nedēļām|:count nedēļas|:count nedēļām',
+ 'w' => '0 nedēļām|:count nedēļas|:count nedēļām',
+ 'day' => '0 dienām|:count dienas|:count dienām',
+ 'd' => '0 dienām|:count dienas|:count dienām',
+ 'hour' => '0 stundām|:count stundas|:count stundām',
+ 'h' => '0 stundām|:count stundas|:count stundām',
+ 'minute' => '0 minūtēm|:count minūtes|:count minūtēm',
+ 'min' => '0 minūtēm|:count minūtes|:count minūtēm',
+ 'second' => '0 sekundēm|:count sekundes|:count sekundēm',
+ 's' => '0 sekundēm|:count sekundes|:count sekundēm',
+ 'ago' => 'pirms :time',
+ 'from_now' => 'pēc :time',
+ 'after' => ':time vēlāk',
+ 'before' => ':time pirms',
+
+ 'year_after' => '0 gadus|:count gadu|:count gadus',
+ 'month_after' => '0 mēnešus|:count mēnesi|:count mēnešus',
+ 'week_after' => '0 nedēļas|:count nedēļu|:count nedēļas',
+ 'day_after' => '0 dienas|:count dienu|:count dienas',
+ 'hour_after' => '0 stundas|:count stundu|:count stundas',
+ 'minute_after' => '0 minūtes|:count minūti|:count minūtes',
+ 'second_after' => '0 sekundes|:count sekundi|:count sekundes',
+
+ 'year_before' => '0 gadus|:count gadu|:count gadus',
+ 'month_before' => '0 mēnešus|:count mēnesi|:count mēnešus',
+ 'week_before' => '0 nedēļas|:count nedēļu|:count nedēļas',
+ 'day_before' => '0 dienas|:count dienu|:count dienas',
+ 'hour_before' => '0 stundas|:count stundu|:count stundas',
+ 'minute_before' => '0 minūtes|:count minūti|:count minūtes',
+ 'second_before' => '0 sekundes|:count sekundi|:count sekundes',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/mk.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/mk.php
new file mode 100644
index 0000000..c5ec12d
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/mk.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count година|:count години',
+ 'month' => ':count месец|:count месеци',
+ 'week' => ':count седмица|:count седмици',
+ 'day' => ':count ден|:count дена',
+ 'hour' => ':count час|:count часа',
+ 'minute' => ':count минута|:count минути',
+ 'second' => ':count секунда|:count секунди',
+ 'ago' => 'пред :time',
+ 'from_now' => ':time од сега',
+ 'after' => 'по :time',
+ 'before' => 'пред :time',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/mn.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/mn.php
new file mode 100644
index 0000000..b26dce5
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/mn.php
@@ -0,0 +1,62 @@
+
+ *
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @translator Batmandakh Erdenebileg
+ */
+
+return array(
+ 'year' => ':count жил',
+ 'y' => ':count жил',
+ 'month' => ':count сар',
+ 'm' => ':count сар',
+ 'week' => ':count долоо хоног',
+ 'w' => ':count долоо хоног',
+ 'day' => ':count өдөр',
+ 'd' => ':count өдөр',
+ 'hour' => ':count цаг',
+ 'h' => ':countц',
+ 'minute' => ':count минут',
+ 'min' => ':countм',
+ 'second' => ':count секунд',
+ 's' => ':countс',
+
+ 'ago' => ':timeн өмнө',
+ 'year_ago' => ':count жилий',
+ 'month_ago' => ':count сары',
+ 'day_ago' => ':count хоногий',
+ 'hour_ago' => ':count цагий',
+ 'minute_ago' => ':count минуты',
+ 'second_ago' => ':count секунды',
+
+ 'from_now' => 'одоогоос :time',
+ 'year_from_now' => ':count жилийн дараа',
+ 'month_from_now' => ':count сарын дараа',
+ 'day_from_now' => ':count хоногийн дараа',
+ 'hour_from_now' => ':count цагийн дараа',
+ 'minute_from_now' => ':count минутын дараа',
+ 'second_from_now' => ':count секундын дараа',
+
+ // Does it required to make translation for before, after as follows? hmm, I think we've made it with ago and from now keywords already. Anyway, I've included it just in case of undesired action...
+ 'after' => ':timeн дараа',
+ 'year_after' => ':count жилий',
+ 'month_after' => ':count сары',
+ 'day_after' => ':count хоногий',
+ 'hour_after' => ':count цагий',
+ 'minute_after' => ':count минуты',
+ 'second_after' => ':count секунды',
+ 'before' => ':timeн өмнө',
+ 'year_before' => ':count жилий',
+ 'month_before' => ':count сары',
+ 'day_before' => ':count хоногий',
+ 'hour_before' => ':count цагий',
+ 'minute_before' => ':count минуты',
+ 'second_before' => ':count секунды',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ms.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ms.php
new file mode 100644
index 0000000..ef57422
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ms.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count tahun',
+ 'y' => ':count tahun',
+ 'month' => ':count bulan',
+ 'm' => ':count bulan',
+ 'week' => ':count minggu',
+ 'w' => ':count minggu',
+ 'day' => ':count hari',
+ 'd' => ':count hari',
+ 'hour' => ':count jam',
+ 'h' => ':count jam',
+ 'minute' => ':count minit',
+ 'min' => ':count minit',
+ 'second' => ':count saat',
+ 's' => ':count saat',
+ 'ago' => ':time yang lalu',
+ 'from_now' => ':time dari sekarang',
+ 'after' => ':time selepas',
+ 'before' => ':time sebelum',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/my.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/my.php
new file mode 100644
index 0000000..e8e491e
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/my.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count နှစ်|:count နှစ်',
+ 'y' => ':count နှစ်|:count နှစ်',
+ 'month' => ':count လ|:count လ',
+ 'm' => ':count လ|:count လ',
+ 'week' => ':count ပတ်|:count ပတ်',
+ 'w' => ':count ပတ်|:count ပတ်',
+ 'day' => ':count ရက်|:count ရက်',
+ 'd' => ':count ရက်|:count ရက်',
+ 'hour' => ':count နာရီ|:count နာရီ',
+ 'h' => ':count နာရီ|:count နာရီ',
+ 'minute' => ':count မိနစ်|:count မိနစ်',
+ 'min' => ':count မိနစ်|:count မိနစ်',
+ 'second' => ':count စက္ကန့်|:count စက္ကန့်',
+ 's' => ':count စက္ကန့်|:count စက္ကန့်',
+ 'ago' => 'လွန်ခဲ့သော :time က',
+ 'from_now' => 'ယခုမှစ၍နောက် :time အကြာ',
+ 'after' => ':time ကြာပြီးနောက်',
+ 'before' => ':time မတိုင်ခင်',
+ 'diff_now' => 'အခုလေးတင်',
+ 'diff_yesterday' => 'မနေ့က',
+ 'diff_tomorrow' => 'မနက်ဖြန်',
+ 'diff_before_yesterday' => 'တမြန်နေ့က',
+ 'diff_after_tomorrow' => 'တဘက်ခါ',
+ 'period_recurrences' => ':count ကြိမ်',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ne.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ne.php
new file mode 100644
index 0000000..0b528df
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ne.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count वर्ष',
+ 'y' => ':count वर्ष',
+ 'month' => ':count महिना',
+ 'm' => ':count महिना',
+ 'week' => ':count हप्ता',
+ 'w' => ':count हप्ता',
+ 'day' => ':count दिन',
+ 'd' => ':count दिन',
+ 'hour' => ':count घण्टा',
+ 'h' => ':count घण्टा',
+ 'minute' => ':count मिनेट',
+ 'min' => ':count मिनेट',
+ 'second' => ':count सेकेण्ड',
+ 's' => ':count सेकेण्ड',
+ 'ago' => ':time पहिले',
+ 'from_now' => ':time देखि',
+ 'after' => ':time पछि',
+ 'before' => ':time अघि',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/nl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/nl.php
new file mode 100644
index 0000000..ec5a88e
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/nl.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count jaar',
+ 'y' => ':count jaar',
+ 'month' => ':count maand|:count maanden',
+ 'm' => ':count maand|:count maanden',
+ 'week' => ':count week|:count weken',
+ 'w' => ':count week|:count weken',
+ 'day' => ':count dag|:count dagen',
+ 'd' => ':count dag|:count dagen',
+ 'hour' => ':count uur',
+ 'h' => ':count uur',
+ 'minute' => ':count minuut|:count minuten',
+ 'min' => ':count minuut|:count minuten',
+ 'second' => ':count seconde|:count seconden',
+ 's' => ':count seconde|:count seconden',
+ 'ago' => ':time geleden',
+ 'from_now' => 'over :time',
+ 'after' => ':time later',
+ 'before' => ':time eerder',
+ 'diff_now' => 'nu',
+ 'diff_yesterday' => 'gisteren',
+ 'diff_tomorrow' => 'morgen',
+ 'diff_after_tomorrow' => 'overmorgen',
+ 'diff_before_yesterday' => 'eergisteren',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/no.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/no.php
new file mode 100644
index 0000000..a6ece06
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/no.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count år|:count år',
+ 'y' => ':count år|:count år',
+ 'month' => ':count måned|:count måneder',
+ 'm' => ':count måned|:count måneder',
+ 'week' => ':count uke|:count uker',
+ 'w' => ':count uke|:count uker',
+ 'day' => ':count dag|:count dager',
+ 'd' => ':count dag|:count dager',
+ 'hour' => ':count time|:count timer',
+ 'h' => ':count time|:count timer',
+ 'minute' => ':count minutt|:count minutter',
+ 'min' => ':count minutt|:count minutter',
+ 'second' => ':count sekund|:count sekunder',
+ 's' => ':count sekund|:count sekunder',
+ 'ago' => ':time siden',
+ 'from_now' => 'om :time',
+ 'after' => ':time etter',
+ 'before' => ':time før',
+ 'diff_now' => 'akkurat nå',
+ 'diff_yesterday' => 'i går',
+ 'diff_tomorrow' => 'i morgen',
+ 'diff_before_yesterday' => 'i forgårs',
+ 'diff_after_tomorrow' => 'i overmorgen',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/oc.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/oc.php
new file mode 100644
index 0000000..e89e94c
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/oc.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+\Symfony\Component\Translation\PluralizationRules::set(function ($number) {
+ return $number == 1 ? 0 : 1;
+}, 'oc');
+
+return array(
+ 'year' => ':count an|:count ans',
+ 'y' => ':count an|:count ans',
+ 'month' => ':count mes|:count meses',
+ 'm' => ':count mes|:count meses',
+ 'week' => ':count setmana|:count setmanas',
+ 'w' => ':count setmana|:count setmanas',
+ 'day' => ':count jorn|:count jorns',
+ 'd' => ':count jorn|:count jorns',
+ 'hour' => ':count ora|:count oras',
+ 'h' => ':count ora|:count oras',
+ 'minute' => ':count minuta|:count minutas',
+ 'min' => ':count minuta|:count minutas',
+ 'second' => ':count segonda|:count segondas',
+ 's' => ':count segonda|:count segondas',
+ 'ago' => 'fa :time',
+ 'from_now' => 'dins :time',
+ 'after' => ':time aprèp',
+ 'before' => ':time abans',
+ 'diff_now' => 'ara meteis',
+ 'diff_yesterday' => 'ièr',
+ 'diff_tomorrow' => 'deman',
+ 'diff_before_yesterday' => 'ièr delà',
+ 'diff_after_tomorrow' => 'deman passat',
+ 'period_recurrences' => ':count còp|:count còps',
+ 'period_interval' => 'cada :interval',
+ 'period_start_date' => 'de :date',
+ 'period_end_date' => 'fins a :date',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/pl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/pl.php
new file mode 100644
index 0000000..2308af2
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/pl.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count rok|:count lata|:count lat',
+ 'y' => ':countr|:countl',
+ 'month' => ':count miesiąc|:count miesiące|:count miesięcy',
+ 'm' => ':countmies',
+ 'week' => ':count tydzień|:count tygodnie|:count tygodni',
+ 'w' => ':counttyg',
+ 'day' => ':count dzień|:count dni|:count dni',
+ 'd' => ':countd',
+ 'hour' => ':count godzina|:count godziny|:count godzin',
+ 'h' => ':countg',
+ 'minute' => ':count minuta|:count minuty|:count minut',
+ 'min' => ':countm',
+ 'second' => ':count sekunda|:count sekundy|:count sekund',
+ 's' => ':counts',
+ 'ago' => ':time temu',
+ 'from_now' => ':time od teraz',
+ 'after' => ':time po',
+ 'before' => ':time przed',
+ 'diff_now' => 'przed chwilą',
+ 'diff_yesterday' => 'wczoraj',
+ 'diff_tomorrow' => 'jutro',
+ 'diff_before_yesterday' => 'przedwczoraj',
+ 'diff_after_tomorrow' => 'pojutrze',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ps.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ps.php
new file mode 100644
index 0000000..15c3296
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ps.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count کال|:count کاله',
+ 'y' => ':countکال|:countکاله',
+ 'month' => ':count مياشت|:count مياشتي',
+ 'm' => ':countمياشت|:countمياشتي',
+ 'week' => ':count اونۍ|:count اونۍ',
+ 'w' => ':countاونۍ|:countاونۍ',
+ 'day' => ':count ورځ|:count ورځي',
+ 'd' => ':countورځ|:countورځي',
+ 'hour' => ':count ساعت|:count ساعته',
+ 'h' => ':countساعت|:countساعته',
+ 'minute' => ':count دقيقه|:count دقيقې',
+ 'min' => ':countدقيقه|:countدقيقې',
+ 'second' => ':count ثانيه|:count ثانيې',
+ 's' => ':countثانيه|:countثانيې',
+ 'ago' => ':time دمخه',
+ 'from_now' => ':time له اوس څخه',
+ 'after' => ':time وروسته',
+ 'before' => ':time دمخه',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/pt.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/pt.php
new file mode 100644
index 0000000..392b121
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/pt.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count ano|:count anos',
+ 'y' => ':count ano|:count anos',
+ 'month' => ':count mês|:count meses',
+ 'm' => ':count mês|:count meses',
+ 'week' => ':count semana|:count semanas',
+ 'w' => ':count semana|:count semanas',
+ 'day' => ':count dia|:count dias',
+ 'd' => ':count dia|:count dias',
+ 'hour' => ':count hora|:count horas',
+ 'h' => ':count hora|:count horas',
+ 'minute' => ':count minuto|:count minutos',
+ 'min' => ':count minuto|:count minutos',
+ 'second' => ':count segundo|:count segundos',
+ 's' => ':count segundo|:count segundos',
+ 'ago' => ':time atrás',
+ 'from_now' => 'em :time',
+ 'after' => ':time depois',
+ 'before' => ':time antes',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php
new file mode 100644
index 0000000..1f84eac
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count ano|:count anos',
+ 'y' => ':counta|:counta',
+ 'month' => ':count mês|:count meses',
+ 'm' => ':countm|:countm',
+ 'week' => ':count semana|:count semanas',
+ 'w' => ':countsem|:countsem',
+ 'day' => ':count dia|:count dias',
+ 'd' => ':countd|:countd',
+ 'hour' => ':count hora|:count horas',
+ 'h' => ':counth|:counth',
+ 'minute' => ':count minuto|:count minutos',
+ 'min' => ':countmin|:countmin',
+ 'second' => ':count segundo|:count segundos',
+ 's' => ':counts|:counts',
+ 'ago' => 'há :time',
+ 'from_now' => 'em :time',
+ 'after' => 'após :time',
+ 'before' => ':time atrás',
+ 'diff_now' => 'agora',
+ 'diff_yesterday' => 'ontem',
+ 'diff_tomorrow' => 'amanhã',
+ 'diff_before_yesterday' => 'anteontem',
+ 'diff_after_tomorrow' => 'depois de amanhã',
+ 'period_recurrences' => 'uma|:count vez',
+ 'period_interval' => 'toda :interval',
+ 'period_start_date' => 'de :date',
+ 'period_end_date' => 'até :date',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ro.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ro.php
new file mode 100644
index 0000000..cc16724
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ro.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => 'un an|:count ani|:count ani',
+ 'y' => 'un an|:count ani|:count ani',
+ 'month' => 'o lună|:count luni|:count luni',
+ 'm' => 'o lună|:count luni|:count luni',
+ 'week' => 'o săptămână|:count săptămâni|:count săptămâni',
+ 'w' => 'o săptămână|:count săptămâni|:count săptămâni',
+ 'day' => 'o zi|:count zile|:count zile',
+ 'd' => 'o zi|:count zile|:count zile',
+ 'hour' => 'o oră|:count ore|:count ore',
+ 'h' => 'o oră|:count ore|:count ore',
+ 'minute' => 'un minut|:count minute|:count minute',
+ 'min' => 'un minut|:count minute|:count minute',
+ 'second' => 'o secundă|:count secunde|:count secunde',
+ 's' => 'o secundă|:count secunde|:count secunde',
+ 'ago' => 'acum :time',
+ 'from_now' => ':time de acum',
+ 'after' => 'peste :time',
+ 'before' => 'acum :time',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ru.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ru.php
new file mode 100644
index 0000000..6a83fb1
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ru.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count год|:count года|:count лет',
+ 'y' => ':count г|:count г|:count л',
+ 'month' => ':count месяц|:count месяца|:count месяцев',
+ 'm' => ':count м|:count м|:count м',
+ 'week' => ':count неделю|:count недели|:count недель',
+ 'w' => ':count н|:count н|:count н',
+ 'day' => ':count день|:count дня|:count дней',
+ 'd' => ':count д|:count д|:count д',
+ 'hour' => ':count час|:count часа|:count часов',
+ 'h' => ':count ч|:count ч|:count ч',
+ 'minute' => ':count минуту|:count минуты|:count минут',
+ 'min' => ':count мин|:count мин|:count мин',
+ 'second' => ':count секунду|:count секунды|:count секунд',
+ 's' => ':count с|:count с|:count с',
+ 'ago' => ':time назад',
+ 'from_now' => 'через :time',
+ 'after' => ':time после',
+ 'before' => ':time до',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sh.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sh.php
new file mode 100644
index 0000000..57f287a
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sh.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+\Symfony\Component\Translation\PluralizationRules::set(function ($number) {
+ return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
+}, 'sh');
+
+return array(
+ 'year' => ':count godina|:count godine|:count godina',
+ 'y' => ':count godina|:count godine|:count godina',
+ 'month' => ':count mesec|:count meseca|:count meseci',
+ 'm' => ':count mesec|:count meseca|:count meseci',
+ 'week' => ':count nedelja|:count nedelje|:count nedelja',
+ 'w' => ':count nedelja|:count nedelje|:count nedelja',
+ 'day' => ':count dan|:count dana|:count dana',
+ 'd' => ':count dan|:count dana|:count dana',
+ 'hour' => ':count čas|:count časa|:count časova',
+ 'h' => ':count čas|:count časa|:count časova',
+ 'minute' => ':count minut|:count minuta|:count minuta',
+ 'min' => ':count minut|:count minuta|:count minuta',
+ 'second' => ':count sekund|:count sekunda|:count sekundi',
+ 's' => ':count sekund|:count sekunda|:count sekundi',
+ 'ago' => 'pre :time',
+ 'from_now' => 'za :time',
+ 'after' => 'nakon :time',
+ 'before' => ':time raniјe',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sk.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sk.php
new file mode 100644
index 0000000..6101344
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sk.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => 'rok|:count roky|:count rokov',
+ 'y' => 'rok|:count roky|:count rokov',
+ 'month' => 'mesiac|:count mesiace|:count mesiacov',
+ 'm' => 'mesiac|:count mesiace|:count mesiacov',
+ 'week' => 'týždeň|:count týždne|:count týždňov',
+ 'w' => 'týždeň|:count týždne|:count týždňov',
+ 'day' => 'deň|:count dni|:count dní',
+ 'd' => 'deň|:count dni|:count dní',
+ 'hour' => 'hodinu|:count hodiny|:count hodín',
+ 'h' => 'hodinu|:count hodiny|:count hodín',
+ 'minute' => 'minútu|:count minúty|:count minút',
+ 'min' => 'minútu|:count minúty|:count minút',
+ 'second' => 'sekundu|:count sekundy|:count sekúnd',
+ 's' => 'sekundu|:count sekundy|:count sekúnd',
+ 'ago' => 'pred :time',
+ 'from_now' => 'za :time',
+ 'after' => 'o :time neskôr',
+ 'before' => ':time predtým',
+ 'year_ago' => 'rokom|:count rokmi|:count rokmi',
+ 'month_ago' => 'mesiacom|:count mesiacmi|:count mesiacmi',
+ 'week_ago' => 'týždňom|:count týždňami|:count týždňami',
+ 'day_ago' => 'dňom|:count dňami|:count dňami',
+ 'hour_ago' => 'hodinou|:count hodinami|:count hodinami',
+ 'minute_ago' => 'minútou|:count minútami|:count minútami',
+ 'second_ago' => 'sekundou|:count sekundami|:count sekundami',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sl.php
new file mode 100644
index 0000000..06686d1
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sl.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count leto|:count leti|:count leta|:count let',
+ 'y' => ':count leto|:count leti|:count leta|:count let',
+ 'month' => ':count mesec|:count meseca|:count mesece|:count mesecev',
+ 'm' => ':count mesec|:count meseca|:count mesece|:count mesecev',
+ 'week' => ':count teden|:count tedna|:count tedne|:count tednov',
+ 'w' => ':count teden|:count tedna|:count tedne|:count tednov',
+ 'day' => ':count dan|:count dni|:count dni|:count dni',
+ 'd' => ':count dan|:count dni|:count dni|:count dni',
+ 'hour' => ':count uro|:count uri|:count ure|:count ur',
+ 'h' => ':count uro|:count uri|:count ure|:count ur',
+ 'minute' => ':count minuto|:count minuti|:count minute|:count minut',
+ 'min' => ':count minuto|:count minuti|:count minute|:count minut',
+ 'second' => ':count sekundo|:count sekundi|:count sekunde|:count sekund',
+ 's' => ':count sekundo|:count sekundi|:count sekunde|:count sekund',
+ 'year_ago' => ':count letom|:count leti|:count leti|:count leti',
+ 'month_ago' => ':count mesecem|:count meseci|:count meseci|:count meseci',
+ 'week_ago' => ':count tednom|:count tednoma|:count tedni|:count tedni',
+ 'day_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi',
+ 'hour_ago' => ':count uro|:count urama|:count urami|:count urami',
+ 'minute_ago' => ':count minuto|:count minutama|:count minutami|:count minutami',
+ 'second_ago' => ':count sekundo|:count sekundama|:count sekundami|:count sekundami',
+ 'ago' => 'pred :time',
+ 'from_now' => 'čez :time',
+ 'after' => 'čez :time',
+ 'before' => 'pred :time',
+ 'diff_now' => 'ravnokar',
+ 'diff_yesterday' => 'včeraj',
+ 'diff_tomorrow' => 'jutri',
+ 'diff_before_yesterday' => 'predvčerajšnjim',
+ 'diff_after_tomorrow' => 'pojutrišnjem',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sq.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sq.php
new file mode 100644
index 0000000..6e138a0
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sq.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count vit|:count vjet',
+ 'y' => ':count vit|:count vjet',
+ 'month' => ':count muaj|:count muaj',
+ 'm' => ':count muaj|:count muaj',
+ 'week' => ':count javë|:count javë',
+ 'w' => ':count javë|:count javë',
+ 'day' => ':count ditë|:count ditë',
+ 'd' => ':count ditë|:count ditë',
+ 'hour' => ':count orë|:count orë',
+ 'h' => ':count orë|:count orë',
+ 'minute' => ':count minutë|:count minuta',
+ 'min' => ':count minutë|:count minuta',
+ 'second' => ':count sekondë|:count sekonda',
+ 's' => ':count sekondë|:count sekonda',
+ 'ago' => ':time më parë',
+ 'from_now' => ':time nga tani',
+ 'after' => ':time pas',
+ 'before' => ':time para',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr.php
new file mode 100644
index 0000000..5a10642
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count godina|:count godine|:count godina',
+ 'y' => ':count godina|:count godine|:count godina',
+ 'month' => ':count mesec|:count meseca|:count meseci',
+ 'm' => ':count mesec|:count meseca|:count meseci',
+ 'week' => ':count nedelja|:count nedelje|:count nedelja',
+ 'w' => ':count nedelja|:count nedelje|:count nedelja',
+ 'day' => ':count dan|:count dana|:count dana',
+ 'd' => ':count dan|:count dana|:count dana',
+ 'hour' => ':count sat|:count sata|:count sati',
+ 'h' => ':count sat|:count sata|:count sati',
+ 'minute' => ':count minut|:count minuta |:count minuta',
+ 'min' => ':count minut|:count minuta |:count minuta',
+ 'second' => ':count sekund|:count sekunde|:count sekunde',
+ 's' => ':count sekund|:count sekunde|:count sekunde',
+ 'ago' => 'pre :time',
+ 'from_now' => ':time od sada',
+ 'after' => 'nakon :time',
+ 'before' => 'pre :time',
+
+ 'year_from_now' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina',
+ 'year_ago' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina',
+
+ 'week_from_now' => '{1} :count nedelju|{2,3,4} :count nedelje|[5,Inf[ :count nedelja',
+ 'week_ago' => '{1} :count nedelju|{2,3,4} :count nedelje|[5,Inf[ :count nedelja',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php
new file mode 100644
index 0000000..2db83ed
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[ :count година',
+ 'y' => ':count г.',
+ 'month' => '{1} :count месец|{2,3,4}:count месеца|[5,Inf[ :count месеци',
+ 'm' => ':count м.',
+ 'week' => '{1} :count недеља|{2,3,4}:count недеље|[5,Inf[ :count недеља',
+ 'w' => ':count нед.',
+ 'day' => '{1,21,31} :count дан|[2,Inf[ :count дана',
+ 'd' => ':count д.',
+ 'hour' => '{1,21} :count сат|{2,3,4,22,23,24}:count сата|[5,Inf[ :count сати',
+ 'h' => ':count ч.',
+ 'minute' => '{1,21,31,41,51} :count минут|[2,Inf[ :count минута',
+ 'min' => ':count мин.',
+ 'second' => '{1,21,31,41,51} :count секунд|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count секунде|[5,Inf[:count секунди',
+ 's' => ':count сек.',
+ 'ago' => 'пре :time',
+ 'from_now' => 'за :time',
+ 'after' => ':time након',
+ 'before' => ':time пре',
+
+ 'year_from_now' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година',
+ 'year_ago' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година',
+
+ 'week_from_now' => '{1} :count недељу|{2,3,4} :count недеље|[5,Inf[ :count недеља',
+ 'week_ago' => '{1} :count недељу|{2,3,4} :count недеље|[5,Inf[ :count недеља',
+
+ 'diff_now' => 'управо сада',
+ 'diff_yesterday' => 'јуче',
+ 'diff_tomorrow' => 'сутра',
+ 'diff_before_yesterday' => 'прекјуче',
+ 'diff_after_tomorrow' => 'прекосутра',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php
new file mode 100644
index 0000000..18214c4
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[ :count година',
+ 'y' => ':count г.',
+ 'month' => '{1} :count мјесец|{2,3,4}:count мјесеца|[5,Inf[ :count мјесеци',
+ 'm' => ':count мј.',
+ 'week' => '{1} :count недјеља|{2,3,4}:count недјеље|[5,Inf[ :count недјеља',
+ 'w' => ':count нед.',
+ 'day' => '{1,21,31} :count дан|[2,Inf[ :count дана',
+ 'd' => ':count д.',
+ 'hour' => '{1,21} :count сат|{2,3,4,22,23,24}:count сата|[5,Inf[ :count сати',
+ 'h' => ':count ч.',
+ 'minute' => '{1,21,31,41,51} :count минут|[2,Inf[ :count минута',
+ 'min' => ':count мин.',
+ 'second' => '{1,21,31,41,51} :count секунд|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count секунде|[5,Inf[:count секунди',
+ 's' => ':count сек.',
+ 'ago' => 'прије :time',
+ 'from_now' => 'за :time',
+ 'after' => ':time након',
+ 'before' => ':time прије',
+
+ 'year_from_now' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година',
+ 'year_ago' => '{1,21,31,41,51} :count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count године|[5,Inf[ :count година',
+
+ 'week_from_now' => '{1} :count недјељу|{2,3,4} :count недјеље|[5,Inf[ :count недјеља',
+ 'week_ago' => '{1} :count недјељу|{2,3,4} :count недјеље|[5,Inf[ :count недјеља',
+
+ 'diff_now' => 'управо сада',
+ 'diff_yesterday' => 'јуче',
+ 'diff_tomorrow' => 'сутра',
+ 'diff_before_yesterday' => 'прекјуче',
+ 'diff_after_tomorrow' => 'прекосјутра',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php
new file mode 100644
index 0000000..2d2e288
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count godine|[0,Inf[ :count godina',
+ 'y' => ':count g.',
+ 'month' => '{1} :count mjesec|{2,3,4}:count mjeseca|[5,Inf[ :count mjeseci',
+ 'm' => ':count mj.',
+ 'week' => '{1} :count nedjelja|{2,3,4}:count nedjelje|[5,Inf[ :count nedjelja',
+ 'w' => ':count ned.',
+ 'day' => '{1,21,31} :count dan|[2,Inf[ :count dana',
+ 'd' => ':count d.',
+ 'hour' => '{1,21} :count sat|{2,3,4,22,23,24}:count sata|[5,Inf[ :count sati',
+ 'h' => ':count č.',
+ 'minute' => '{1,21,31,41,51} :count minut|[2,Inf[ :count minuta',
+ 'min' => ':count min.',
+ 'second' => '{1,21,31,41,51} :count sekund|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count sekunde|[5,Inf[:count sekundi',
+ 's' => ':count sek.',
+ 'ago' => 'prije :time',
+ 'from_now' => 'za :time',
+ 'after' => ':time nakon',
+ 'before' => ':time prije',
+
+ 'year_from_now' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina',
+ 'year_ago' => '{1,21,31,41,51} :count godinu|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54} :count godine|[5,Inf[ :count godina',
+
+ 'week_from_now' => '{1} :count nedjelju|{2,3,4} :count nedjelje|[5,Inf[ :count nedjelja',
+ 'week_ago' => '{1} :count nedjelju|{2,3,4} :count nedjelje|[5,Inf[ :count nedjelja',
+
+ 'diff_now' => 'upravo sada',
+ 'diff_yesterday' => 'juče',
+ 'diff_tomorrow' => 'sutra',
+ 'diff_before_yesterday' => 'prekjuče',
+ 'diff_after_tomorrow' => 'preksutra',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php
new file mode 100644
index 0000000..7ebf6f0
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php
@@ -0,0 +1,12 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return require __DIR__.'/sr_Latn_ME.php';
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sv.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sv.php
new file mode 100644
index 0000000..89a03b4
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sv.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count år|:count år',
+ 'y' => ':count år|:count år',
+ 'month' => ':count månad|:count månader',
+ 'm' => ':count månad|:count månader',
+ 'week' => ':count vecka|:count veckor',
+ 'w' => ':count vecka|:count veckor',
+ 'day' => ':count dag|:count dagar',
+ 'd' => ':count dag|:count dagar',
+ 'hour' => ':count timme|:count timmar',
+ 'h' => ':count timme|:count timmar',
+ 'minute' => ':count minut|:count minuter',
+ 'min' => ':count minut|:count minuter',
+ 'second' => ':count sekund|:count sekunder',
+ 's' => ':count sekund|:count sekunder',
+ 'ago' => ':time sedan',
+ 'from_now' => 'om :time',
+ 'after' => ':time efter',
+ 'before' => ':time före',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/sw.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/sw.php
new file mode 100644
index 0000000..52f0342
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/sw.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => 'mwaka 1|miaka :count',
+ 'y' => 'mwaka 1|miaka :count',
+ 'month' => 'mwezi 1|miezi :count',
+ 'm' => 'mwezi 1|miezi :count',
+ 'week' => 'wiki 1|wiki :count',
+ 'w' => 'wiki 1|wiki :count',
+ 'day' => 'siku 1|siku :count',
+ 'd' => 'siku 1|siku :count',
+ 'hour' => 'saa 1|masaa :count',
+ 'h' => 'saa 1|masaa :count',
+ 'minute' => 'dakika 1|dakika :count',
+ 'min' => 'dakika 1|dakika :count',
+ 'second' => 'sekunde 1|sekunde :count',
+ 's' => 'sekunde 1|sekunde :count',
+ 'ago' => ':time ziliyopita',
+ 'from_now' => ':time kwanzia sasa',
+ 'after' => ':time baada',
+ 'before' => ':time kabla',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/th.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/th.php
new file mode 100644
index 0000000..88bb4ac
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/th.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count ปี',
+ 'y' => ':count ปี',
+ 'month' => ':count เดือน',
+ 'm' => ':count เดือน',
+ 'week' => ':count สัปดาห์',
+ 'w' => ':count สัปดาห์',
+ 'day' => ':count วัน',
+ 'd' => ':count วัน',
+ 'hour' => ':count ชั่วโมง',
+ 'h' => ':count ชั่วโมง',
+ 'minute' => ':count นาที',
+ 'min' => ':count นาที',
+ 'second' => ':count วินาที',
+ 's' => ':count วินาที',
+ 'ago' => ':timeที่แล้ว',
+ 'from_now' => ':timeต่อจากนี้',
+ 'after' => ':timeหลังจากนี้',
+ 'before' => ':timeก่อน',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/tr.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/tr.php
new file mode 100644
index 0000000..6a9dfed
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/tr.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count yıl',
+ 'y' => ':count yıl',
+ 'month' => ':count ay',
+ 'm' => ':count ay',
+ 'week' => ':count hafta',
+ 'w' => ':count hafta',
+ 'day' => ':count gün',
+ 'd' => ':count gün',
+ 'hour' => ':count saat',
+ 'h' => ':count saat',
+ 'minute' => ':count dakika',
+ 'min' => ':count dakika',
+ 'second' => ':count saniye',
+ 's' => ':count saniye',
+ 'ago' => ':time önce',
+ 'from_now' => ':time sonra',
+ 'after' => ':time sonra',
+ 'before' => ':time önce',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/uk.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/uk.php
new file mode 100644
index 0000000..8d08eaa
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/uk.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count рік|:count роки|:count років',
+ 'y' => ':count рік|:count роки|:count років',
+ 'month' => ':count місяць|:count місяці|:count місяців',
+ 'm' => ':count місяць|:count місяці|:count місяців',
+ 'week' => ':count тиждень|:count тижні|:count тижнів',
+ 'w' => ':count тиждень|:count тижні|:count тижнів',
+ 'day' => ':count день|:count дні|:count днів',
+ 'd' => ':count день|:count дні|:count днів',
+ 'hour' => ':count година|:count години|:count годин',
+ 'h' => ':count година|:count години|:count годин',
+ 'minute' => ':count хвилину|:count хвилини|:count хвилин',
+ 'min' => ':count хвилину|:count хвилини|:count хвилин',
+ 'second' => ':count секунду|:count секунди|:count секунд',
+ 's' => ':count секунду|:count секунди|:count секунд',
+ 'ago' => ':time тому',
+ 'from_now' => 'через :time',
+ 'after' => ':time після',
+ 'before' => ':time до',
+ 'diff_now' => 'щойно',
+ 'diff_yesterday' => 'вчора',
+ 'diff_tomorrow' => 'завтра',
+ 'diff_before_yesterday' => 'позавчора',
+ 'diff_after_tomorrow' => 'післязавтра',
+ 'period_recurrences' => 'один раз|:count рази|:count разів',
+ 'period_interval' => 'кожні :interval',
+ 'period_start_date' => 'з :date',
+ 'period_end_date' => 'до :date',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/ur.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/ur.php
new file mode 100644
index 0000000..3c5f7ed
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/ur.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count سال',
+ 'month' => ':count ماه',
+ 'week' => ':count ہفتے',
+ 'day' => ':count روز',
+ 'hour' => ':count گھنٹے',
+ 'minute' => ':count منٹ',
+ 'second' => ':count سیکنڈ',
+ 'ago' => ':time پہلے',
+ 'from_now' => ':time بعد',
+ 'after' => ':time بعد',
+ 'before' => ':time پہلے',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/uz.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/uz.php
new file mode 100644
index 0000000..1cb6f71
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/uz.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count yil',
+ 'y' => ':count yil',
+ 'month' => ':count oy',
+ 'm' => ':count oy',
+ 'week' => ':count hafta',
+ 'w' => ':count hafta',
+ 'day' => ':count kun',
+ 'd' => ':count kun',
+ 'hour' => ':count soat',
+ 'h' => ':count soat',
+ 'minute' => ':count daqiqa',
+ 'min' => ':count daq',
+ 'second' => ':count soniya',
+ 's' => ':count s',
+ 'ago' => ':time avval',
+ 'from_now' => ':time dan keyin',
+ 'after' => ':time keyin',
+ 'before' => ':time oldin',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/vi.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/vi.php
new file mode 100644
index 0000000..3f9838d
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/vi.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count năm',
+ 'y' => ':count năm',
+ 'month' => ':count tháng',
+ 'm' => ':count tháng',
+ 'week' => ':count tuần',
+ 'w' => ':count tuần',
+ 'day' => ':count ngày',
+ 'd' => ':count ngày',
+ 'hour' => ':count giờ',
+ 'h' => ':count giờ',
+ 'minute' => ':count phút',
+ 'min' => ':count phút',
+ 'second' => ':count giây',
+ 's' => ':count giây',
+ 'ago' => ':time trước',
+ 'from_now' => ':time từ bây giờ',
+ 'after' => ':time sau',
+ 'before' => ':time trước',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/zh.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/zh.php
new file mode 100644
index 0000000..9e1f6ca
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/zh.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count年',
+ 'y' => ':count年',
+ 'month' => ':count个月',
+ 'm' => ':count个月',
+ 'week' => ':count周',
+ 'w' => ':count周',
+ 'day' => ':count天',
+ 'd' => ':count天',
+ 'hour' => ':count小时',
+ 'h' => ':count小时',
+ 'minute' => ':count分钟',
+ 'min' => ':count分钟',
+ 'second' => ':count秒',
+ 's' => ':count秒',
+ 'ago' => ':time前',
+ 'from_now' => '距现在:time',
+ 'after' => ':time后',
+ 'before' => ':time前',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php b/core/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php
new file mode 100644
index 0000000..c848723
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+return array(
+ 'year' => ':count年',
+ 'y' => ':count年',
+ 'month' => ':count月',
+ 'm' => ':count月',
+ 'week' => ':count週',
+ 'w' => ':count週',
+ 'day' => ':count天',
+ 'd' => ':count天',
+ 'hour' => ':count小時',
+ 'h' => ':count小時',
+ 'minute' => ':count分鐘',
+ 'min' => ':count分鐘',
+ 'second' => ':count秒',
+ 's' => ':count秒',
+ 'ago' => ':time前',
+ 'from_now' => '距現在:time',
+ 'after' => ':time後',
+ 'before' => ':time前',
+);
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php b/core/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php
new file mode 100644
index 0000000..4d83b0c
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php
@@ -0,0 +1,37 @@
+app['events'];
+ if ($events instanceof EventDispatcher || $events instanceof Dispatcher) {
+ $events->listen(class_exists('Illuminate\Foundation\Events\LocaleUpdated') ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed', function () use ($service) {
+ $service->updateLocale();
+ });
+ $service->updateLocale();
+ }
+ }
+
+ public function updateLocale()
+ {
+ $translator = $this->app['translator'];
+ if ($translator instanceof Translator || $translator instanceof IlluminateTranslator) {
+ Carbon::setLocale($translator->getLocale());
+ }
+ }
+
+ public function register()
+ {
+ // Needed for Laravel < 5.3 compatibility
+ }
+}
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Translator.php b/core/vendor/nesbot/carbon/src/Carbon/Translator.php
new file mode 100644
index 0000000..12115b0
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Translator.php
@@ -0,0 +1,143 @@
+addLoader('array', new Translation\Loader\ArrayLoader());
+ parent::__construct($locale, $formatter, $cacheDir, $debug);
+ }
+
+ /**
+ * Reset messages of a locale (all locale if no locale passed).
+ * Remove custom messages and reload initial messages from matching
+ * file in Lang directory.
+ *
+ * @param string|null $locale
+ *
+ * @return bool
+ */
+ public function resetMessages($locale = null)
+ {
+ if ($locale === null) {
+ static::$messages = array();
+
+ return true;
+ }
+
+ if (file_exists($filename = __DIR__.'/Lang/'.$locale.'.php')) {
+ static::$messages[$locale] = require $filename;
+ $this->addResource('array', static::$messages[$locale], $locale);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Init messages language from matching file in Lang directory.
+ *
+ * @param string $locale
+ *
+ * @return bool
+ */
+ protected function loadMessagesFromFile($locale)
+ {
+ if (isset(static::$messages[$locale])) {
+ return true;
+ }
+
+ return $this->resetMessages($locale);
+ }
+
+ /**
+ * Set messages of a locale and take file first if present.
+ *
+ * @param string $locale
+ * @param array $messages
+ *
+ * @return $this
+ */
+ public function setMessages($locale, $messages)
+ {
+ $this->loadMessagesFromFile($locale);
+ $this->addResource('array', $messages, $locale);
+ static::$messages[$locale] = array_merge(
+ isset(static::$messages[$locale]) ? static::$messages[$locale] : array(),
+ $messages
+ );
+
+ return $this;
+ }
+
+ /**
+ * Get messages of a locale, if none given, return all the
+ * languages.
+ *
+ * @param string|null $locale
+ *
+ * @return array
+ */
+ public function getMessages($locale = null)
+ {
+ return $locale === null ? static::$messages : static::$messages[$locale];
+ }
+
+ /**
+ * Set the current translator locale and indicate if the source locale file exists
+ *
+ * @param string $locale locale ex. en
+ *
+ * @return bool
+ */
+ public function setLocale($locale)
+ {
+ $locale = preg_replace_callback('/[-_]([a-z]{2,})/', function ($matches) {
+ // _2-letters is a region, _3+-letters is a variant
+ return '_'.call_user_func(strlen($matches[1]) > 2 ? 'ucfirst' : 'strtoupper', $matches[1]);
+ }, strtolower($locale));
+
+ if ($this->loadMessagesFromFile($locale)) {
+ parent::setLocale($locale);
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/core/vendor/nesbot/carbon/src/Carbon/Upgrade.php b/core/vendor/nesbot/carbon/src/Carbon/Upgrade.php
new file mode 100644
index 0000000..26449ff
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/Carbon/Upgrade.php
@@ -0,0 +1,150 @@
+ '5.8.0',
+ 'laravel/cashier' => '9.0.1',
+ 'illuminate/support' => '5.8.0',
+ 'laravel/dusk' => '5.0.0',
+ );
+
+ protected static $otherLibraries = array(
+ 'spatie/laravel-analytics' => '3.6.4',
+ 'jenssegers/date' => '3.5.0',
+ );
+
+ /**
+ * @param \UpdateHelper\UpdateHelper $helper
+ */
+ public function check(UpdateHelper $helper)
+ {
+ $helper->write(array(
+ 'Carbon 1 is deprecated, see how to migrate to Carbon 2.',
+ 'https://carbon.nesbot.com/docs/#api-carbon-2',
+ ));
+
+ if (static::SUGGEST_ON_UPDATE || static::ASK_ON_UPDATE || $helper->getIo()->isVerbose()) {
+ $laravelUpdate = array();
+
+ foreach (static::$laravelLibraries as $name => $version) {
+ if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) {
+ $laravelUpdate[$name] = $version;
+ }
+ }
+
+ if (count($laravelUpdate)) {
+ $output = array(
+ ' Please consider upgrading your Laravel dependencies to be compatible with Carbon 2:',
+ );
+
+ foreach ($laravelUpdate as $name => $version) {
+ $output[] = " - $name at least to version $version";
+ }
+
+ $output[] = '';
+ $output[] = " If you can't update Laravel, check https://carbon.nesbot.com/ to see how to";
+ $output[] = ' install Carbon 2 using alias version and our adapter kylekatarnls/laravel-carbon-2';
+ $output[] = '';
+
+ $helper->write($output);
+ }
+
+ foreach (static::$otherLibraries as $name => $version) {
+ if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) {
+ $helper->write(" Please consider upgrading $name at least to $version to be compatible with Carbon 2.\n");
+ }
+ }
+
+ if (static::ASK_ON_UPDATE) {
+ static::askForUpgrade($helper);
+
+ return;
+ }
+ }
+
+ $path = implode(DIRECTORY_SEPARATOR, array('.', 'vendor', 'bin', 'upgrade-carbon'));
+
+ if (!file_exists($path)) {
+ $path = realpath(__DIR__.'/../../bin/upgrade-carbon');
+ }
+
+ $helper->write(
+ ' You can run '.escapeshellarg($path).
+ ' to get help in updating carbon and other frameworks and libraries that depend on it.'
+ );
+ }
+
+ private static function getUpgradeQuestion($upgrades)
+ {
+ $message = "Do you want us to try the following upgrade:\n";
+
+ foreach ($upgrades as $name => $version) {
+ $message .= " - $name: $version\n";
+ }
+
+ return $message.'[Y/N] ';
+ }
+
+ public static function askForUpgrade(UpdateHelper $helper, $upgradeIfNotInteractive = false)
+ {
+ $upgrades = array(
+ 'nesbot/carbon' => '^2.0.0',
+ );
+
+ foreach (array(static::$laravelLibraries, static::$otherLibraries) as $libraries) {
+ foreach ($libraries as $name => $version) {
+ if ($helper->hasAsDependency($name) && $helper->isDependencyLesserThan($name, $version)) {
+ $upgrades[$name] = "^$version";
+ }
+ }
+ }
+
+ $shouldUpgrade = $helper->isInteractive()
+ ? $helper->getIo()->askConfirmation(static::getUpgradeQuestion($upgrades))
+ : $upgradeIfNotInteractive;
+
+ if ($shouldUpgrade) {
+ $helper->setDependencyVersions($upgrades)->update();
+ }
+ }
+
+ public static function upgrade(ScriptEvent $event = null)
+ {
+ if (!$event) {
+ $composer = new Composer();
+ $baseDir = __DIR__.'/../..';
+
+ if (file_exists("$baseDir/autoload.php")) {
+ $baseDir .= '/..';
+ }
+
+ $composer->setConfig(new Config(true, $baseDir));
+ $event = new ScriptEvent(
+ 'upgrade-carbon',
+ $composer,
+ new ConsoleIO(new StringInput(''), new ConsoleOutput(), new HelperSet(array(
+ new QuestionHelper(),
+ )))
+ );
+ }
+
+ static::askForUpgrade(new UpdateHelper($event), true);
+ }
+}
diff --git a/core/vendor/nesbot/carbon/src/JsonSerializable.php b/core/vendor/nesbot/carbon/src/JsonSerializable.php
new file mode 100644
index 0000000..d34060b
--- /dev/null
+++ b/core/vendor/nesbot/carbon/src/JsonSerializable.php
@@ -0,0 +1,18 @@
+json_encode,
+ * which is a value of any type other than a resource.
+ *
+ * @since 5.4.0
+ */
+ public function jsonSerialize();
+ }
+}
diff --git a/core/vendor/nikic/php-parser/.gitignore b/core/vendor/nikic/php-parser/.gitignore
new file mode 100644
index 0000000..8c7db2a
--- /dev/null
+++ b/core/vendor/nikic/php-parser/.gitignore
@@ -0,0 +1,4 @@
+vendor/
+composer.lock
+grammar/kmyacc.exe
+grammar/y.output
diff --git a/core/vendor/nikic/php-parser/.travis.yml b/core/vendor/nikic/php-parser/.travis.yml
new file mode 100644
index 0000000..4716915
--- /dev/null
+++ b/core/vendor/nikic/php-parser/.travis.yml
@@ -0,0 +1,32 @@
+language: php
+
+sudo: false
+
+cache:
+ directories:
+ - $HOME/.composer/cache
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - nightly
+ - hhvm
+
+install:
+ - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then composer require satooshi/php-coveralls '~1.0'; fi
+ - composer install --prefer-dist
+
+matrix:
+ allow_failures:
+ - php: nightly
+ fast_finish: true
+
+script:
+ - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then vendor/bin/phpunit --coverage-clover build/logs/clover.xml; else vendor/bin/phpunit; fi
+ - if [ $TRAVIS_PHP_VERSION = '7.0' ]; then test_old/run-php-src.sh; fi
+
+after_success:
+ if [ $TRAVIS_PHP_VERSION = '5.6' ]; then php vendor/bin/coveralls; fi
+
diff --git a/core/vendor/nikic/php-parser/CHANGELOG.md b/core/vendor/nikic/php-parser/CHANGELOG.md
new file mode 100644
index 0000000..0ae6b7e
--- /dev/null
+++ b/core/vendor/nikic/php-parser/CHANGELOG.md
@@ -0,0 +1,151 @@
+Version 2.1.2-dev
+-----------------
+
+Nothing yet.
+
+Version 2.1.1 (2016-09-16)
+--------------------------
+
+### Changed
+
+* The pretty printer will now escape all control characters in the range `\x00-\x1F` inside double
+ quoted strings. If no special escape sequence is available, an octal escape will be used.
+* The quality of the error recovery has been improved. In particular unterminated expressions should
+ be handled more gracefully.
+* The PHP 7 parser will now generate a parse error for `$var =& new Obj` assignments.
+* Comments on free-standing code blocks will no be retained as comments on the first statement in
+ the code block.
+
+Version 2.1.0 (2016-04-19)
+--------------------------
+
+### Fixed
+
+* Properly support `B""` strings (with uppercase `B`) in a number of places.
+* Fixed reformatting of indented parts in a certain non-standard comment style.
+
+### Added
+
+* Added `dumpComments` option to node dumper, to enable dumping of comments associated with nodes.
+* Added `Stmt\Nop` node, that is used to collect comments located at the end of a block or at the
+ end of a file (without a following node with which they could otherwise be associated).
+* Added `kind` attribute to `Expr\Exit` to distinguish between `exit` and `die`.
+* Added `kind` attribute to `Scalar\LNumber` to distinguish between decimal, binary, octal and
+ hexadecimal numbers.
+* Added `kind` attribtue to `Expr\Array` to distinguish between `array()` and `[]`.
+* Added `kind` attribute to `Scalar\String` and `Scalar\Encapsed` to distinguish between
+ single-quoted, double-quoted, heredoc and nowdoc string.
+* Added `docLabel` attribute to `Scalar\String` and `Scalar\Encapsed`, if it is a heredoc or
+ nowdoc string.
+* Added start file offset information to `Comment` nodes.
+* Added `setReturnType()` method to function and method builders.
+* Added `-h` and `--help` options to `php-parse` script.
+
+### Changed
+
+* Invalid octal literals now throw a parse error in PHP 7 mode.
+* The pretty printer takes all the new attributes mentioned in the previous section into account.
+* The protected `AbstractPrettyPrinter::pComments()` method no longer returns a trailing newline.
+* The bundled autoloader supports library files being stored in a different directory than
+ `PhpParser` for easier downstream distribution.
+
+### Deprecated
+
+* The `Comment::setLine()` and `Comment::setText()` methods have been deprecated. Construct new
+ objects instead.
+
+### Removed
+
+* The internal (but public) method `Scalar\LNumber::parse()` has been removed. A non-internal
+ `LNumber::fromString()` method has been added instead.
+
+Version 2.0.1 (2016-02-28)
+--------------------------
+
+### Fixed
+
+* `declare() {}` and `declare();` are not semantically equivalent and will now result in different
+ ASTs. The format case will have an empty `stmts` array, while the latter will set `stmts` to
+ `null`.
+* Magic constants are now supported as semi-reserved keywords.
+* A shebang line like `#!/usr/bin/env php` is now allowed at the start of a namespaced file.
+ Previously this generated an exception.
+* The `prettyPrintFile()` method will not strip a trailing `?>` from the raw data that follows a
+ `__halt_compiler()` statement.
+* The `prettyPrintFile()` method will not strip an opening `slice()` which takes a subslice of a name.
+
+### Changed
+
+* `PhpParser\Parser` is now an interface, implemented by `Parser\Php5`, `Parser\Php7` and
+ `Parser\Multiple`. The `Multiple` parser will try multiple parsers, until one succeeds.
+* Token constants are now defined on `PhpParser\Parser\Tokens` rather than `PhpParser\Parser`.
+* The `Name->set()`, `Name->append()`, `Name->prepend()` and `Name->setFirst()` methods are
+ deprecated in favor of `Name::concat()` and `Name->slice()`.
+* The `NodeTraverser` no longer clones nodes by default. The old behavior can be restored by
+ passing `true` to the constructor.
+* The constructor for `Scalar` nodes no longer has a default value. E.g. `new LNumber()` should now
+ be written as `new LNumber(0)`.
+
+---
+
+**This changelog only includes changes from the 2.0 series. For older changes see the
+[1.x series changelog](https://github.com/nikic/PHP-Parser/blob/1.x/CHANGELOG.md) and the
+[0.9 series changelog](https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md).**
\ No newline at end of file
diff --git a/core/vendor/nikic/php-parser/LICENSE b/core/vendor/nikic/php-parser/LICENSE
new file mode 100644
index 0000000..443210b
--- /dev/null
+++ b/core/vendor/nikic/php-parser/LICENSE
@@ -0,0 +1,31 @@
+Copyright (c) 2011 by Nikita Popov.
+
+Some rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * The names of the contributors may not be used to endorse or
+ promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/core/vendor/nikic/php-parser/README.md b/core/vendor/nikic/php-parser/README.md
new file mode 100644
index 0000000..28c565c
--- /dev/null
+++ b/core/vendor/nikic/php-parser/README.md
@@ -0,0 +1,96 @@
+PHP Parser
+==========
+
+[![Build Status](https://travis-ci.org/nikic/PHP-Parser.svg?branch=master)](https://travis-ci.org/nikic/PHP-Parser) [![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
+
+This is a PHP 5.2 to PHP 7.0 parser written in PHP. Its purpose is to simplify static code analysis and
+manipulation.
+
+[**Documentation for version 2.x**][doc_master] (stable; for running on PHP >= 5.4; for parsing PHP 5.2 to PHP 7.0).
+
+[Documentation for version 1.x][doc_1_x] (unsupported; for running on PHP >= 5.3; for parsing PHP 5.2 to PHP 5.6).
+
+In a Nutshell
+-------------
+
+The parser turns PHP source code into an abstract syntax tree. For example, if you pass the following code into the
+parser:
+
+```php
+ Expr_AssignOp_BitwiseAnd
+Expr_AssignBitwiseOr => Expr_AssignOp_BitwiseOr
+Expr_AssignBitwiseXor => Expr_AssignOp_BitwiseXor
+Expr_AssignConcat => Expr_AssignOp_Concat
+Expr_AssignDiv => Expr_AssignOp_Div
+Expr_AssignMinus => Expr_AssignOp_Minus
+Expr_AssignMod => Expr_AssignOp_Mod
+Expr_AssignMul => Expr_AssignOp_Mul
+Expr_AssignPlus => Expr_AssignOp_Plus
+Expr_AssignShiftLeft => Expr_AssignOp_ShiftLeft
+Expr_AssignShiftRight => Expr_AssignOp_ShiftRight
+
+Expr_BitwiseAnd => Expr_BinaryOp_BitwiseAnd
+Expr_BitwiseOr => Expr_BinaryOp_BitwiseOr
+Expr_BitwiseXor => Expr_BinaryOp_BitwiseXor
+Expr_BooleanAnd => Expr_BinaryOp_BooleanAnd
+Expr_BooleanOr => Expr_BinaryOp_BooleanOr
+Expr_Concat => Expr_BinaryOp_Concat
+Expr_Div => Expr_BinaryOp_Div
+Expr_Equal => Expr_BinaryOp_Equal
+Expr_Greater => Expr_BinaryOp_Greater
+Expr_GreaterOrEqual => Expr_BinaryOp_GreaterOrEqual
+Expr_Identical => Expr_BinaryOp_Identical
+Expr_LogicalAnd => Expr_BinaryOp_LogicalAnd
+Expr_LogicalOr => Expr_BinaryOp_LogicalOr
+Expr_LogicalXor => Expr_BinaryOp_LogicalXor
+Expr_Minus => Expr_BinaryOp_Minus
+Expr_Mod => Expr_BinaryOp_Mod
+Expr_Mul => Expr_BinaryOp_Mul
+Expr_NotEqual => Expr_BinaryOp_NotEqual
+Expr_NotIdentical => Expr_BinaryOp_NotIdentical
+Expr_Plus => Expr_BinaryOp_Plus
+Expr_ShiftLeft => Expr_BinaryOp_ShiftLeft
+Expr_ShiftRight => Expr_BinaryOp_ShiftRight
+Expr_Smaller => Expr_BinaryOp_Smaller
+Expr_SmallerOrEqual => Expr_BinaryOp_SmallerOrEqual
+
+Scalar_ClassConst => Scalar_MagicConst_Class
+Scalar_DirConst => Scalar_MagicConst_Dir
+Scalar_FileConst => Scalar_MagicConst_File
+Scalar_FuncConst => Scalar_MagicConst_Function
+Scalar_LineConst => Scalar_MagicConst_Line
+Scalar_MethodConst => Scalar_MagicConst_Method
+Scalar_NSConst => Scalar_MagicConst_Namespace
+Scalar_TraitConst => Scalar_MagicConst_Trait
+```
+
+These changes may affect custom pretty printers and code comparing the return value of `Node::getType()` to specific
+strings.
+
+### Miscellaneous
+
+ * The classes `Template` and `TemplateLoader` have been removed. You should use some other [code generation][code_gen]
+ project built on top of PHP-Parser instead.
+
+ * The `PrettyPrinterAbstract::pStmts()` method now emits a leading newline if the statement list is not empty.
+ Custom pretty printers should remove the explicit newline before `pStmts()` calls.
+
+ Old:
+
+ ```php
+ public function pStmt_Trait(PHPParser_Node_Stmt_Trait $node) {
+ return 'trait ' . $node->name
+ . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
+ }
+ ```
+
+ New:
+
+ ```php
+ public function pStmt_Trait(Stmt\Trait_ $node) {
+ return 'trait ' . $node->name
+ . "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
+ }
+ ```
+
+ [code_gen]: https://github.com/nikic/PHP-Parser/wiki/Projects-using-the-PHP-Parser#code-generation
\ No newline at end of file
diff --git a/core/vendor/nikic/php-parser/UPGRADE-2.0.md b/core/vendor/nikic/php-parser/UPGRADE-2.0.md
new file mode 100644
index 0000000..1c61de5
--- /dev/null
+++ b/core/vendor/nikic/php-parser/UPGRADE-2.0.md
@@ -0,0 +1,74 @@
+Upgrading from PHP-Parser 1.x to 2.0
+====================================
+
+### PHP version requirements
+
+PHP-Parser now requires PHP 5.4 or newer to run. It is however still possible to *parse* PHP 5.2 and
+PHP 5.3 source code, while running on a newer version.
+
+### Creating a parser instance
+
+Parser instances should now be created through the `ParserFactory`. Old direct instantiation code
+will not work, because the parser class was renamed.
+
+Old:
+
+```php
+use PhpParser\Parser, PhpParser\Lexer;
+$parser = new Parser(new Lexer\Emulative);
+```
+
+New:
+
+```php
+use PhpParser\ParserFactory;
+$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
+```
+
+The first argument to `ParserFactory` determines how different PHP versions are handled. The
+possible values are:
+
+ * `ParserFactory::PREFER_PHP7`: Try to parse code as PHP 7. If this fails, try to parse it as PHP 5.
+ * `ParserFactory::PREFER_PHP5`: Try to parse code as PHP 5. If this fails, try to parse it as PHP 7.
+ * `ParserFactory::ONLY_PHP7`: Parse code as PHP 7.
+ * `ParserFactory::ONLY_PHP5`: Parse code as PHP 5.
+
+For most practical purposes the difference between `PREFER_PHP7` and `PREFER_PHP5` is mainly whether
+a scalar type hint like `string` will be stored as `'string'` (PHP 7) or as `new Name('string')`
+(PHP 5).
+
+To use a custom lexer, pass it as the second argument to the `create()` method:
+
+```php
+use PhpParser\ParserFactory;
+$lexer = new MyLexer;
+$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer);
+```
+
+### Rename of the `PhpParser\Parser` class
+
+`PhpParser\Parser` is now an interface, which is implemented by `Parser\Php5`, `Parser\Php7` and
+`Parser\Multiple`. Parser tokens are now defined in `Parser\Tokens`. If you use the `ParserFactory`
+described above to create your parser instance, these changes should have no further impact on you.
+
+### Removal of legacy aliases
+
+All legacy aliases for classes have been removed. This includes the old non-namespaced `PHPParser_`
+classes, as well as the classes that had to be renamed for PHP 7 support.
+
+### Deprecations
+
+The `set()`, `setFirst()`, `append()` and `prepend()` methods of the `Node\Name` class have been
+deprecated. Instead `Name::concat()` and `Name->slice()` should be used.
+
+### Miscellaneous
+
+* The `NodeTraverser` no longer clones nodes by default. If you want to restore the old behavior,
+ pass `true` to the constructor.
+* The legacy node format has been removed. If you use custom nodes, they are now expected to
+ implement a `getSubNodeNames()` method.
+* The default value for `Scalar` node constructors was removed. This means that something like
+ `new LNumber()` should be replaced by `new LNumber(0)`.
+* String parts of encapsed strings are now represented using `Scalar\EncapsStringPart` nodes, while
+ previously raw strings were used. This affects the `parts` child of `Scalar\Encaps` and
+ `Expr\ShellExec`.
\ No newline at end of file
diff --git a/core/vendor/nikic/php-parser/bin/php-parse b/core/vendor/nikic/php-parser/bin/php-parse
new file mode 100644
index 0000000..974eb1a
--- /dev/null
+++ b/core/vendor/nikic/php-parser/bin/php-parse
@@ -0,0 +1,173 @@
+#!/usr/bin/env php
+ array(
+ 'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
+)));
+$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer);
+$dumper = new PhpParser\NodeDumper(['dumpComments' => true]);
+$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
+$serializer = new PhpParser\Serializer\XML;
+
+$traverser = new PhpParser\NodeTraverser();
+$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
+
+foreach ($files as $file) {
+ if (strpos($file, ' Code $code\n";
+ } else {
+ if (!file_exists($file)) {
+ die("File $file does not exist.\n");
+ }
+
+ $code = file_get_contents($file);
+ echo "====> File $file:\n";
+ }
+
+ try {
+ $stmts = $parser->parse($code);
+ } catch (PhpParser\Error $e) {
+ if ($attributes['with-column-info'] && $e->hasColumnInfo()) {
+ $startLine = $e->getStartLine();
+ $endLine = $e->getEndLine();
+ $startColumn = $e->getStartColumn($code);
+ $endColumn = $e->getEndColumn($code);
+ $message .= $e->getRawMessage() . " from $startLine:$startColumn to $endLine:$endColumn";
+ } else {
+ $message = $e->getMessage();
+ }
+
+ die($message . "\n");
+ }
+
+ foreach ($operations as $operation) {
+ if ('dump' === $operation) {
+ echo "==> Node dump:\n";
+ echo $dumper->dump($stmts), "\n";
+ } elseif ('pretty-print' === $operation) {
+ echo "==> Pretty print:\n";
+ echo $prettyPrinter->prettyPrintFile($stmts), "\n";
+ } elseif ('serialize-xml' === $operation) {
+ echo "==> Serialized XML:\n";
+ echo $serializer->serialize($stmts), "\n";
+ } elseif ('var-dump' === $operation) {
+ echo "==> var_dump():\n";
+ var_dump($stmts);
+ } elseif ('resolve-names' === $operation) {
+ echo "==> Resolved names.\n";
+ $stmts = $traverser->traverse($stmts);
+ }
+ }
+}
+
+function showHelp($error = '') {
+ if ($error) {
+ echo $error . "\n\n";
+ }
+ die(<<